using BenchmarkTools
f(x) = x
struct Wrapper
f::Function
end
function evaluate(f, x)
return f(x)
end
function evaluate(wrapper::Wrapper, x)
return wrapper.f(x)
end
function test(f, n)
[evaluate(f, x) for x in 1:n]
end
w = Wrapper(f)
global n
for n in [10, 100, 1000, 10000, 100000]
test(f, n)
@time test(f, n)
test(w, n)
@time test(w, n)
end
println()
@btime test(f, 10)
@btime test(w, 10)
@btime test(f, 100)
@btime test(w, 100)
@btime test(f, 1000)
@btime test(w, 1000)
@btime test(f, 10000)
@btime test(w, 10000)
@btime test(f, 100000)
@btime test(w, 100000)
println()
Thank you very much for your excellent answer. FYI, I was orienting towards NLSolversBase.jl
mutable struct OnceDifferentiable{TF, TDF, TX} <: AbstractObjective
f # objective
df # (partial) derivative of objective
fdf # objective and (partial) derivative of objective
F::TF # cache for f output
DF::TDF # cache for df output
x_f::TX # x used to evaluate f (stored in F)
x_df::TX # x used to evaluate df (stored in DF)
f_calls::Vector{Int}
df_calls::Vector{Int}
end
It’s not always the case that restricting the types of fields to something concrete will improve the performance of code, it usually depends on how it’s being used. Over-parameterising types can sometimes lead to strain on the compiler since passing a different Function as .f might need to recompile a bunch of new code.
It may be that in OnceDifferentiable it doesn’t actually matter, or having an untyped field was more useful. Only the authors of that code would be able to provide a definite answer. Usually best to benchmark those kind of decisions if there’s some doubt.