Conversation
By introducing an internal CallableVal parametric type it is possible to store callable objects as type parameters and automatically unwrap them at the call site.
|
Some notes:
struct SugarKelp{TL, SO, PS} <: UnwrapValueFields
temperature_limit :: TL
solver :: SO
params :: PS
function SugarKelp(FT = Float64, valued=true;
temperature_limit::TL = LinearOptimalTemperatureRange{FT}(),
solver::SO = NewtonRaphsonSolver{FT, Int}(; atol = eps(FT(1e-9))),
kwargs...
) where {TL, SO}
params = SugarKelpParameters{FT}(; kwargs...)
if valued
return new{CallableVal{temperature_limit}, CallableVal{solver}, Val{params}}(CallableVal(temperature_limit), CallableVal(solver), Val(params))
else
return new{TL, SO, typeof(params)}(temperature_limit, solver, params)
end
end
end i.e. remove the |
|
I'm not sure about the value of the trade off between the added source code complexity vs the potential peformance benefits. It would be interesting to see if we can manufacture a case where this gives tangible peformance improvements. I don't know if some automation and abstraction could perhaps off set some of the added complexity making it worth while? |
|
This could possibly be developed as an extension to KernelAbstractions syntax (because it requires both @kernel function func_with_params(a, b, @Val c, d)
# etc
endunder the hood this would generate something func_with_params(a, b, ::Val{c}, d) where c = func_with_params(a, b, c, d)then on the user side, you would have the option of Val-wrapping or not when you build the kernel. |
Related to #320.
It is marked as draft since it is intended to be a starting point for the discussion on whether something like that should be merged into the code, and to show off some proposal on how it can be done. Hence, I have only applied it to the
SugarKelpIn terms of performance, as discussed in the issue, it doesn't seem to give much. As expected, the register usage is significantly reduced, but it is not enough to increase occupancy. Small saving related to the fact that some computation can be compile-time evaluated is minimal (<2%) and on the verge of measurable. Based on that alone I would say it is probably not worth the effort... but on the other hand, for some kernels, in some circumstances, it may be enough to push a kernel to the higher occupancy band, and the performance improvements when that happens can be significant :-/
In terms of implementation I went with what I thought to be least invasive, which is to move numeric parameters to separate
@kwdefstruct and embed it as a parameter of the model struct (as suggested by @glwagner). Then, to 'unwrap' the value fromVal{V}when using.(getproperty) access automatically, the model struct can subtypeUnwrapValueFields. I wish it could be done with a 'Holy Trait', but it would result horrible type piracy (specialisingBase.getproperty(::Any, ::Symbol)🏴☠️ 😬 ).The alternative would be to use a custom 'property-getter' function. I went with what I went, but I don't have any preference for either.
For the callable types, I have added a custom
Valparametric type so they will be automatically unwrapped when called. As far as I am able to tell (looking into the generated code + poking it with@code_...macros) it does specialise the call for an instance. But I am still a bit uneasy if there isn't some subtle trap/edge case lurking around.