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