With type stable arguments, f
, g
, and h
are identical, as you surmise.
Here’s a benchmark for dynamic dispatch;
julia> f(a) = a+a
f (generic function with 1 method)
julia> @generated g(a) = :(a+a)
g (generic function with 1 method)
julia> h(a::Float64) = a+a
h (generic function with 1 method)
julia> h(a::Int64) = a+a
h (generic function with 2 methods)
julia> using BenchmarkTools
julia> const dynamic = Number[0, 0.0]
julia> for fn in [f, g, h]
@btime $fn(dynamic[1]) + $fn(dynamic[2])
end
47.345 ns (2 allocations: 32 bytes)
41.269 ns (2 allocations: 32 bytes)
40.773 ns (2 allocations: 32 bytes)
Here we see that h
is the fastest, followed by g
and then f
is much slower. In fact f
is slower because of a compiler optimization gone wrong: f
is simple enough to be inlined, so the compiler does so, and this inlines a dynamic dispatch to +
, which is more expensive than dynamically dispatching to g
or h
which have much simpler method tables. In general this situation is rather rare; inlining doesn’t often hurt performance. So the result of this particular artificial benchmark should be taken with the understanding that in practice, slowdown due to inlining is not very common. If our functions are more expensive, perhaps to the extent that they are no longer feasible to inline, then there will be little difference between f
and h
(f
would be a little faster because of the simpler method table).