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
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