Hi! This is maybe a bit of a random question, so I hope that this is the right place to ask it - my apologies if not.
In the Manual in the section about Pre-allocating outputs under the Performance tips (Pre-allocating outputs) there are two examples: one where they do and one where they don’t pre-allocate the values in a function containing a loop over 10^7 values. Here are the two examples they provide:
Version 1 (without preallocation)
julia> function xinc(x)
return [x, x+1, x+2]
end;
julia> function loopinc()
y = 0
for i = 1:10^7
ret = xinc(i)
y += ret[2]
end
return y
end;
and Version 2 (with preallocating):
julia> function xinc!(ret::AbstractVector{T}, x::T) where T
ret[1] = x
ret[2] = x+1
ret[3] = x+2
nothing
end;
julia> function loopinc_prealloc()
ret = Vector{Int}(undef, 3)
y = 0
for i = 1:10^7
xinc!(ret, i)
y += ret[2]
end
return y
end;
and these are the performance results:
julia> @time loopinc()
0.529894 seconds (40.00 M allocations: 1.490 GiB, 12.14% gc time)
50000015000000
julia> @time loopinc_prealloc()
0.030850 seconds (6 allocations: 288 bytes)
50000015000000
Obviously pre-allocating is better, but they acknowledge that writing it like this can often be a bit ugly and cumbersome, which is why the syntax a .= f.(x) exists. And they say:
However, for “vectorized” (element-wise) functions, the convenient syntax
x .= f.(y)
can be used for in-place operations with fused loops and no temporary arrays (see the dot syntax for vectorizing functions).
Now coming to my questions: how would I use the suggested syntax x .= f.(y) to rewrite the above example? I was not able to figure that out.
Also I tried to create a little example myself to compare the efficiency with and without dot indexation, however, in my example the version with the dot index was actually worse than the other one and is not clear to me why. My example:
f(x) = x^2
y = rand(10^9);
function withDot(x)
a = similar(x)
a .= f.(x)
return a
end
function withoutDot(x)
b = f.(x)
return b
end
@time withDot(y);
2.911010 seconds (2 allocations: 7.451 GiB, 22.19% gc time)
@time withoutDot(y);
1.736906 seconds (2 allocations: 7.451 GiB)
Here I don’t understand why the first one is worse and why the garbage collection time is so large.
To summarise: I don’t understand in what situation and how I would use the a .= f.(x) syntax correctly to improve the performance. I would much appreciate if someone could maybe provide some examples and explain it a bit more (since unfortunately I seem to not have properly understood how it is meant to work from reading the Manual).
Thanks for the help! I hope this is the right place to be asking something like this. (I only started learning Julia about 2 days ago)