Hi there!
I am working on some Code that is supposed to be a demonstration of how you can create fast and efficient code with just a few easy, readable lines in Julia. Sadly one of the functions turns out to be not typestable with some really weird behaviour, and I dont know why.
Some Background: f
, the function in question, should iterate over a combination of indices of a Matrix and reduce them to a single value. Since I want to apply this later to the RGB-channels of an Image, I use multiple dispatch and define a method for f(x::Array{T,3})
that just sums f
over all relevant slices.
To not bother you with irrelevant details I stripped away as much details as was possible while still recreating the instability. Here’s the Code:
function f(v::AbstractMatrix{T}) where T
sum(1 for r in (1,2))
end
function f(ν::AbstractArray{T,3}) where {T}
return @views sum( f(ν[:, :, i]) for i in 1:size(ν, 3))
end
@code_warntype f(zeros(2, 2, 2))
MethodInstance for f(::Array{Float64, 3})
from f(ν::AbstractArray{T, 3}) where T in Main at In[79]:1
Static Parameters
T = Float64
Arguments
#self#::Core.Const(f)
ν::Array{Float64, 3}
Locals
#59::var"#59#60"{Array{Float64, 3}}
Body::Any
1 ─ %1 = Main.:(var"#59#60")::Core.Const(var"#59#60")
│ %2 = Core.typeof(ν)::Core.Const(Array{Float64, 3})
│ %3 = Core.apply_type(%1, %2)::Core.Const(var"#59#60"{Array{Float64, 3}})
│ (#59 = %new(%3, ν))
│ %5 = #59::var"#59#60"{Array{Float64, 3}}
│ %6 = Main.size(ν, 3)::Int64
│ %7 = (1:%6)::Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])
│ %8 = Base.Generator(%5, %7)::Core.PartialStruct(Base.Generator{UnitRange{Int64}, var"#59#60"{Array{Float64, 3}}}, Any[var"#59#60"{Array{Float64, 3}}, Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])])
│ %9 = Main.sum(%8)::Any
└── return %9
Note the returntype of Any
. Here are some parts that I find odd about this:
- Even though
f
, given an appropriate matrixview
is typestable,f(::Array{T,3})
is not. - Furthermore, if I check this fact with
@code_warntype f(zeros(2,2))
, the instability just ‘goes away’;
if I run
then suddenly everything is typestable. This is confirmed by some Performance testing: when I callfunction f(v::AbstractMatrix{T}) where T sum(1 for r in (1,2)) end function f(ν::AbstractArray{T,3}) where {T} return @views sum( f(ν[:, :, i]) for i in 1:size(ν, 3)) end @code_warntype f(zeros(2, 2)) @code_warntype f(zeros(2, 2, 2))
I get 5 allocations per call, but when I runfunction testf() a=zeros(2,2,2) @time f(a) end testf()
@code_warntype f(zeros(2, 2))
beforehand the allocations disappear. - If I replace
f
in the generator with any other scalar valued function, e.g.maximum
, the instability goes away. - If I just let the first method
return 2
, the instability goes away, even though this is of course equivalent (which the compiler knows, as of@code_llvm f(zeros(2,2)
).
When trying to recreate the problem I recommend to regularly restart the kernel and only run the proper sections, due to the instable instability ( ) .
I work in a Jupyter Notebook on Windows with a 1.7.2 kernel.
Any help of whats going on here is welcome.