Description
openedon Apr 2, 2021
In JuliaData/DataFrames.jl#2691 I needed to use Ref{Any}
trick to avoid excessive specialization. Unfortunately I was not able to achieve the required level of despecialization using @nospecialize
. The issue is quite complex and we discussed with @nalimilan a lot what to do with it. Since we really do not understand in full how to avoid specialization I ask the question here (as on Slack it probably will get quickly lost).
The problem is that @nospecialize
does not guarantee that the argument is not specialized. There are cases when not having this is prohibitive (as each additional compilation of method instance is costly in itself). Here is an MWE using MethodAnalysis.jl:
julia> using MethodAnalysis
julia> @noinline f(@nospecialize(x)) = map(x, [1,2,3])
f (generic function with 1 method)
julia> g(x) = f(x)
g (generic function with 1 method)
julia> g(sin);
julia> methodinstances(f)
2-element Vector{Core.MethodInstance}:
MethodInstance for f(::Function)
MethodInstance for f(::Any)
julia> g(Int);
julia> methodinstances(f)
3-element Vector{Core.MethodInstance}:
MethodInstance for f(::Function)
MethodInstance for f(::Any)
MethodInstance for f(::Type)
julia> g(Float64);
julia> methodinstances(f)
3-element Vector{Core.MethodInstance}:
MethodInstance for f(::Function)
MethodInstance for f(::Any)
MethodInstance for f(::Type)
and I get 3 method instances of f
instead of 1 as I wanted.
Now in a fresh session additionally this happens:
julia> using MethodAnalysis
julia> @noinline f(@nospecialize(x)) = map(x, [1,2,3])
f (generic function with 1 method)
julia> g(x::Base.Callable) = f(x)
g (generic function with 1 method)
julia> g(sin);
julia> methodinstances(f)
2-element Vector{Core.MethodInstance}:
MethodInstance for f(::Function)
MethodInstance for f(::Any)
julia> g(Int);
julia> methodinstances(f)
3-element Vector{Core.MethodInstance}:
MethodInstance for f(::Function)
MethodInstance for f(::Any)
MethodInstance for f(::Type{Int64})
julia> g(Float64);
julia> methodinstances(f)
4-element Vector{Core.MethodInstance}:
MethodInstance for f(::Function)
MethodInstance for f(::Any)
MethodInstance for f(::Type{Int64})
MethodInstance for f(::Type{Float64})
and this time it is even worse - for each type passed a new method instance is generated.
I have more examples if needed, but they all boil down to one issue: how to tell the compiler that unconditionally only one method instance should be generated for a given argument (as commented above in JuliaData/DataFrames.jl#2691 we ended up using Ref{Any}
to guarantee this, but this is kind of ugly and maybe there is a better way to do it).