I don’t think heap allocation is “bad” - in most cases it is wanted and needed. First of all stack is usually limited to few megabytes and stack space is tightly assigned to a specific thread.
Consider such example
input1 = rand(1000000)
input2 = rand(1000000)
We need to calculate sum of squares of element-wise differences
function with_heap_allocations(input1, intput2)
result = sum((input1 .- input2).^2)
function no_heap_allocation(input1, input2)
result2 = 0.0
for i in 1:length(input1)
result2 += (input1[i] - input2[i])^2
julia> @time with_heap_allocations(input1, input2)
0.002924 seconds (13 allocations: 7.630 MiB)
julia> @time no_heap_allocation(input1, input2)
0.001143 seconds (5 allocations: 176 bytes)
In the first example broadcast operator is doing in-memory projection of its results before passing it to
sum so that is why you see few megabytes allocated.
The latter one is just iterating data that already are in the memory, so no significant allocations are seen.
Anyway, I don’t think the second implementation is better. It is much longer and harder to reason about. In most cases you should not care about those allocations too much, these days memory is cheap and garbage collectors are very sophisticated.
Also Julia is a bit different than general purpose languages with functional features (C#, Haskell, Java). For instance in Julia
map function is doing in-memory projection of the result, but in other languages you might expect some “lazy” collection that could be further processed and the actual code is executed when you try to look into the final data without any allocations for intermediate results. In Julia I observed that iterating
zip function result seems to have such laziness property, example:
sum((pair -> (pair- pair)^2), zip(input1, input2))