Hi, I am familiar with what a closure is (a function that captures state outside its scope) but I am interested in how they work in Julia so that I can understand performance.
In Performance Tips there is a section dedicated to closures (Performance of captured variable).
Namely it explains that the following is poor performance:
function abmult(r::Int)
if r < 0
r = -r
end
f = x -> x * r
return f
end
And the next is better:
function abmult2(r0::Int)
r::Int = r0
if r < 0
r = -r
end
f = x -> x * r
return f
end
And lastly the below is best:
function abmult3(r::Int)
if r < 0
r = -r
end
f = let r = r
x -> x * r
end
return f
end
I do not see how any more information is added in any of the examples (i.e if I were a compiler I would compile them all the same). It is clear to me that r is an Int when it is captured. It is also clear to me that no other piece of code can change r. So why is it not clear to the compiler?
Basically I think without a technical explanation of some internals I can’t understand what is going on in this example or any use of functions as values in general (not just functions that capture state outside their scope, a.k.a closures). Another thing I don’t understand is how a function that looks like what is below is compiled:
call_function(f, x) = begin
f(x)
end
Is a new specialization for call_function created on every invocation with a different f?
For example:
call_alot_of_functions(xs) = begin
squares = similar(xs)
for (i, x) in enumerate(xs)
f = y -> y * x
squares[i] = call_function(f, x)
end
squares
end
Is this going to be horribly slow? Is f going to be of a different type on each iteration of the loop? Leading to call_function having to be specialized on each iteration? And is f itself going to be recompiled each time?
Surely using closures and anonymous functions isn’t super unperformant given their prevalent use, but I just can’t wrap my mind about how they work internally and feel like it is a hole in my understanding of how the compiler will treat the code that I write