Not sure if I’m misunderstanding you, but it doesn’t seem like that discarding the return is the reason for the performance hit but rather the difference between: f(u) = u and f = (u) -> u
Another example:
julia> f!(u) = (u.=u.^2)
f! (generic function with 1 method)
julia> g!(u) = (f!(u))
g! (generic function with 1 method)
julia> h! = (u) -> (f!(u))
(::#3) (generic function with 1 method)
julia> u = Float64[1,2,3]
3-element Array{Float64,1}:
1.0
2.0
3.0
julia> using BenchmarkTools
julia> @benchmark f!($u)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 10.922 ns (0.00% GC)
median time: 11.223 ns (0.00% GC)
mean time: 11.282 ns (0.00% GC)
maximum time: 76.766 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 999
julia> @benchmark g!($u)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 10.923 ns (0.00% GC)
median time: 11.219 ns (0.00% GC)
mean time: 11.348 ns (0.00% GC)
maximum time: 61.750 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 999
julia> @benchmark h!($u)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 29.161 ns (0.00% GC)
median time: 29.177 ns (0.00% GC)
mean time: 30.473 ns (0.00% GC)
maximum time: 110.651 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 995
Also not sure why FunctionWrappers is necessary in your example, you call the in-place versions anyways.
Huh, I’m surprised that doesn’t inline the function call and end up as not cost… ?
I want to do this as an option to stop specialization on functions in DiffEq, but I cannot guarantee that a user’s mutating function will actually return nothing even if it doesn’t matter what they return, and the FunctionWrapper does care about that.
In your example, f! is a normal function, but h! is a global variable that could contain anything. You’re at least paying the cost of dynamic dispatch.