Consider
struct data{M}
b::Int
end
get(::data{M}) where M = M
f(x::data) = x.b + get(x)
g(x::data{M}) where M = x.b + M
x = data{7}(8)
Then
julia> @code_warntype get(x)
Body::Int64
1 1 ─ return $(Expr(:static_parameter, 1)) │
julia> @code_warntype f(x)
Body::Int64
1 1 ─ %1 = (Base.getfield)(x, :b)::Int64 │╻ getproperty
│ %2 = (Base.add_int)(%1, 7)::Int64 │╻ +
└── return %2 │
julia> @code_warntype g(x)
Body::Int64
1 1 ─ %1 = (Base.getfield)(x, :b)::Int64 │╻ getproperty
│ %2 = $(Expr(:static_parameter, 1))::Core.Compiler.Const(7, false) │
│ %3 = (Base.add_int)(%1, %2)::Int64 │╻ +
└── return %3
To me it looks like the definition of f
is better and more efficient than the definition of g
because the get
function gets inlined into f
and provides more detailed type information, while g
has to rely on a more mysterious type called Core.Compiler.Const
, not knowing that M
will be an Int
for x
.
julia> @btime f($x)
0.025 ns (0 allocations: 0 bytes)
15
julia> @btime g($x)
0.025 ns (0 allocations: 0 bytes)
15
With this trivial example, the performance is the same, but I still think that f
might be better than g
… especially when the functions have more complex logic, which could be optimized by knowing the value.
Any thoughts on this? Could someone explain Core.Compiler.Const
in more detail?