I wonder what your preferred ways of initializing a read-only variable using imperative code are. This becomes important, e.g., when there are closures involved and Julia wants to box the variable.
Something like
function g(x) = nothing # or something... who knows
function f(x::Integer, a::Integer)
# Initialize y
y = 1
# let's pretend this sum can't be reformulated to something in closed form
for i in 1:x
y = y + i
end
# From this point on, y is basically constant
# [...]
sum(g(y + b) for b in 1:a)
end
My ideas were
function f(x, a)
y = 1
for i in 1:x
y = y + i
end
let y = y
sum(g(y + b) for b in 1:a)
end
end
and
function f(x::Integer, a::Integer)
y = begin
j = 1
for i in 1:x
j = j + i
end
j
end
sum(g(y + b) for b in 1:a)
end
I believe, those two variants should be equivalent. Although I am not completely certain. Personally I went with the first option, as it felt more concise to me.
What are your preferred ways of preventing a Box there and why?
My preference is to write a specific function for initialization if possible. Out of the two proposed, the second feels “better”, only I’d write it as
y = let y = 1
for i in 1:x
y = y + i
end
y
end
There’s also the “Ref trick”:
function f(x::Integer, a::Integer)
# Initialize y
y = Ref(1)
for i in 1:x
y[] = y[] + i
end
sum(g(y[] + b) for b in 1:a)
end
Now, there is no binding change for y, so no boxing needed.
Julia compiler is free to decide if a struct (mutable or not) is stack- or heap-allocated. Ref(1) can be stack allocated if the compiler can prove it is safe. My guess is that arrays cannot be stack allocated because they don’t have a fixed size. But heap allocation of a Ref must be cheaper than array, too, because arrays have to store more meta information.
In this case, @btime shows no allocations with Ref for simple initialization procedures, but switches to heap allocation if there’s something complicated in the loop. So, using one of your initial solutions is a faster way, and both seem equivalent in performance to me.
But Ref is useful if y has to be mutated inside the closure or if you want to share a mutable value between multiple closures.