I’m trying to understand under what circumstances Julia would trigger a compilation for “bad” Julia codes, so I use MethodAnalysis.jl to help the diagnosis. My first assumption is that each creation of a method instance in Julia indicates a native code object is generated.
Let’s start with a simple case:
julia> using MethodAnalysis
julia> function count_number(A::AbstractArray)
rst = 0
for x in A
rst += count_number(x)
end
return rst
end
count_number (generic function with 1 method)
julia> count_number(x::Number) = 1
count_number (generic function with 2 method)
julia> count_number(x) = 0
count_number (generic function with 3 methods)
julia> count_number([1, 2, 3])
3
julia> methodinstances(count_number)
2-element Vector{Core.MethodInstance}:
MethodInstance for count_number(::Vector{Int64})
MethodInstance for count_number(::Int64)
If we restart Julia and instead execute count_number([1, 2, "julia", 3.0])
. Without checking methodinstances
result, I’m expecting it to return 4 method instances:
count_number(::Vector{Any})
count_number(::Int)
count_number(::Float64)
count_number(::String)
But instead, it returns:
julia> methodinstances(count_number)
7-element Vector{Core.MethodInstance}:
MethodInstance for count_number(::Vector{Any})
MethodInstance for count_number(::AbstractArray)
MethodInstance for count_number(::Number)
MethodInstance for count_number(::Int64)
MethodInstance for count_number(::Float64)
MethodInstance for count_number(::Any)
MethodInstance for count_number(::String)
This is a bit surprising to me. Why would Julia ever try to generate count_number(::AbstractArray)
when count_number(::Vector{Any})
is sufficient?
My first guess is that count_number
’s recursively calling itself complicates this, so I made a simpler version of it:
julia> function count_number(A::AbstractArray)
rst = 0
for x in A
rst += _count_number(x)
end
return rst
end
count_number (generic function with 1 method)
julia> _count_number(x::Number) = 1
_count_number (generic function with 1 method)
julia> _count_number(x) = 0
_count_number (generic function with 2 methods)
julia> count_number([1, 2, "julia", 3.0])
3
julia> methodinstances(count_number)
1-element Vector{Core.MethodInstance}:
MethodInstance for count_number(::Vector{Any})
julia> methodinstances(_count_number)
2-element Vector{Core.MethodInstance}:
MethodInstance for _count_number(::Number)
MethodInstance for _count_number(::Any)
This raises more questions:
- why Julia generates the non-specialized
_count_number(::Number)
and_count_number(::Any)
instead of the specialized_count_number(::Int64)
,_count_number(::Float64)
and_count_number(::String)
? - if
_count_number(::Number)
and_count_number(::Any)
is sufficient, why would the recursive version generatecount_number(::Int64)
,count_number(::Float64)
andcount_number(::String)
?
I assume @tim.holy would be interested in these questions?