In practice I may want to write this function (only r.x is the object being considered, other parts are less relevant)
function f!(r, I)
for i=I
r.x[i] = rand(Int)
end
end
Assume now the structure r.x is fixed throughout. So a foolproof way to write this would be
function g!(r, I)
x = r.x # introduce an additional local name (I write `x` here)
for i=I
x[i] = rand(Int)
end
end
But g! is lengthier. I wonder if julia’s compiler can directly perform the transformation to g! under the hood so I can just keep writing f! at the front end. Can I?
Test
Here is a simple test. But in practice my r.x may mean a field access from a NamedTuple r as well
function test(N)
r = Ref(rand(Int, N))
I = Base.OneTo(N)
@time g!(r, I)
@time f!(r, I)
@time g!(r, I)
@time f!(r, I)
end
test(9999999)
The pedantic answer is “It depends on what r and I are.” Julia functions have unconstrained polymorphism, so without knowing what r and I are, this function could do literally anything.
That said, a somewhat safe assumption here would be that r is some form of possibly mutable struct containing a field x which has an array in it, and is using generic methods for getproperty and setindex!, and I is some well behaved iterable of integer indices. In this case, the answer is maybe, this is the sort of thing julia’s compiler is often good at hoisting out of loops, but there’s often some catches.
However, you may run into trouble when r is a mutable type, because then it’s really up to the compiler to decide if it is legal to hoist the pointer loading out of the loop. Your benchmark would appear to suggest that no, in this case the compiler decides not to perform the hoisting. You can check the code_llvm output of f! and g! to see for yourself what exactly it decides to do.