In How to optimise Julia code: A practical guide, Jakob Nissen suggests that one way to optimize code + reduce allocations is to
- Preallocate buffers once and reuse them multiple times
I’m wondering if someone has a walkthrough of an actual example comparing a “repeatedly allocate” solution to a “preallocate buffers and reuse” solution. I think I understand the idea, but I’m wondering how to follow the advice in practice.
For example, I have a calculation like the following:
struct ResultData
value::Float64
result_type::Bool
end
function calculate_and_use(x, y, z)
if y > z
temp = ResultData(y + z, true)
else
temp = ResultData(y * z, false)
end
if temp.result_type
x + z
else
x - z
end
end
Please ignore the implementation details—calculate_and_use is any involved calculation that involves an allocation of an object, here played by ResultData, which has some overhead.
Let’s say calculate_and_use has to be called millions of times by some function, so each time there’s an allocation of a ResultData object which is then thrown away and, voila, re-allocated a split second later. (Not that this seems to matter, but ResultData could actually be a fixed-length Vector{ResultData} or similar, but in any case: it’s created, used, and then thrown away.)
What would be the analogue of pre-allocating a buffer in this context? Would I have some global buf = ResultData(0.0,false) that just gets overwritten? (But this violates “Use immutable structs over mutable struct when mutation is not needed,” as well as violating the dictum not to use globals.)
If not, then what is the right way to follow the preallocation advice in this context?
Or have I misunderstood the preallocation advice…and it is only applicable in other contexts?
Any suggestions appreciated; thank you!