Reducing memory allocations when storing vectors to fields of a mutable type

I have a mutable type called Data

mutable struct Data
        data_a::Vector{Int64}
        data_b::Vector{Int64}
end

I get two vectors that are of type Vector{Float64} from a solution of an ODE and want to construct the type Data field’s being the two vectors.

I am using a function to do so something like this

function store_vectors!(input::Data, solution)
    times = 0.0:0.01:10.0
    states = solution(times)

    input.data_a = [state[1] for state in states]
    input.data_b = [state[2] for state in states]
end

The ODE used StaticArrays.jl to improve the performance which is working as intended but as soon as I store the vectors in Data the allocations jump considerably and I feel like there is a way to decrease the allocations.

Any thoughts/ideas as to ways to improve this?

I was initially thinking to initialise the type Data with a fixed size Vector and then assigning data to it.

Unless you intend to reassign the fields of Data, you can make the struct immutable and simply return a new Data object.

Your function allocates because [state[1] for state in states] creates a new array. Depending on the type/shape of states, this may be superfluos (what is the type of states?).

Vector{SVector{4, Float64}}

And I’m having issues slicing it.

I may have found a solution.

Rather than using state = solution(times) and then allocating them.

I can take solution from the input and slice it e.g. solution[1, :] and use setfield!.

This line allocates a new array for the right-hand-side every time you execute it, so it’s hardly surprising that it will allocate. It has nothing to do with whether you store the result in a mutable struct.

If you want to avoid allocation, you need to overwrite a pre-allocated vector. If you don’t know the size that you need in advance, you can always resize it as needed, for example:

resize!(input.data_a, length(states)) .= getindex.(states, 1)

Thank you @stevengj . This is what I had in mind but couldn’t figure out how to do so.

I’m guessing if size is known the pre-allocated vector can be overwritten using something like

input.data_a .= *insert vector of correct type and length here*

With the key bit being the .= which is used to “reuse” existing memory?

No. The right hand side cannot be a new vector. If you want to avoid allocating, it has to be a “dot call” broadcast operation that is “fused” with the assignment on the left-hand-side.

See the manual: Functions · The Julia Language

(Or just write a loop, of course.)

1 Like