Allocations are often something that seem to appear rather unpredictably when you do more complicated things… Even if you’re aware of all the most typical causes of allocations, something new pops up every time. This tends to slow down my progress more than anything else. This time I decided to make a minimal example and post it here. Is this particular behavior expected, a bug, or just how things are?
When you have a recursive function that uses a callback and the kwargs... syntax, allocations start to appear. If you use the regular keyword argument syntax or don’t call the callback, no allocations.
test(l; limit=10) = (l >= limit)
function recur1(func, l=0; kwargs...) # Causes allocations
if test(l; kwargs...)
return func(l)
end
recur1(func, l+1; kwargs...)
end
function recur2(func, l=0; limit=10) # No allocations
if test(l; limit)
return func(l)
end
recur2(func, l+1; limit)
end
function recur3(func, l=0; kwargs...) # No allocations
if test(l; kwargs...)
return identity(l)
end
recur3(func, l+1; kwargs...)
end
# Compile and test
recur1(identity) # returns 10
recur2(identity) # returns 10
recur3(identity) # returns 10
recur1(identity, limit=5) # returns 5
recur2(identity, limit=5) # returns 5
recur3(identity, limit=5) # returns 5
julia> @allocated recur1(identity)
0
julia> @allocated recur1(identity, limit=1)
0
julia> @allocated recur1(identity, limit=2)
32
julia> @allocated recur1(identity, limit=3)
64
julia> @allocated recur2(identity, limit=5)
0
julia> @allocated recur3(identity, limit=5)
0
As you can see, as soon as you pass a keyword argument to func1 it allocates starting from depth 2, with more allocations the deeper you go in recursion.
You’re right, adding a type parameter does the trick! Now that you say it, I actually remember that functions don’t specialize on function arguments that are not called within the function unless a type parameter is specified. I’ll try to find a link that…
I’ve seen that before but this stuff is quite non-obvious and it’s hard to make the connection when the problem seemed to come from the kwargs.... I think callback functions in particular are a plentiful source of weird allocations. In some case I concluded that a callback in a recursive function caused allocations if the callback function had enough many lines of code, no matter what I did (I tried the specialization trick). Making a minimal example was obviously not simple so I didn’t try…