Sure, sorry, I just wanted to point out that the compiler will in general be able to do more optimizations with immutable types.
Concerning the original post:
A natural way to obtain what you originally wanted here is simply to return a copy of the value instead of trying to modify it:
julia> function test_function(s)
s2 = TestStruct(2,s.b)
return s2
end
test_function (generic function with 1 method)
julia> a = TestStruct(1,rand(1_000));
julia> a = test_function(a)
TestStruct(2, ...
You might think: But then I am just copying the struct! Indeed you are, but this is fast and allocation free (edit: actually not true if the field itself is mutable, which is the case here), because immutable structs are generally stored in the fast stack memory. This is no different from doing
x = 1
x = 2*x
except that your struct has more fields than x
.
I understand that @set
does no miracle here, it just conveniently does that copy without one having to define the structure explicitly. And there is no gain in performance:
julia> function change_vec1!(vec)
for s in vec
s = TestStruct(2*s.a,s.b)
end
end
change_vec1! (generic function with 1 method)
julia> function change_vec2!(vec)
for s in vec
@set s.a = 2*s.a
end
end
change_vec2! (generic function with 1 method)
julia> vec = [ TestStruct(rand(1:10),rand(1_000)) for i in 1:10_000 ];
julia> @btime change_vec1!(vec)
5.036 μs (0 allocations: 0 bytes)
julia> vec = [ TestStruct(rand(1:10),rand(1_000)) for i in 1:10_000 ];
julia> @btime change_vec2!(vec)
5.081 μs (0 allocations: 0 bytes)
edit: these benchmarks are wrong as pointed below. (it is true that @set
is the same as copying the struct, but in these cases the vector of structs is not being modified).