Unwanted allocations when updating type field

Hello! Physics PhD student here using Julia for a research project.

I have a question regarding what seems to be unnecessary extra memory allocations in my program.

I am using a custom type along the lines of

abstract type SuperType end

mutable struct MyStruct <: SuperType
           A::Vector{Float64}
end

As the program runs, I want to update the values the field A for the instances of MyStruct, e.g.

S = MyStruct([0., 0., 0.])

@time S.A = [1,1,1]

Which shows 2 allocations for a total of 160 bytes. While this is not an issue for a few updates, my program involves updating these fields thousands to millions of times per run.

Any insight is appreciated!

An alternative would be to make your vector field immutable and to update it elementwise using broadcasting:

using BenchmarkTools

mutable struct MyMutableStruct
    A::Vector{Float64}
end

struct MyImmutableStruct
    A::Vector{Float64}
end

n = 10_000
S1 = MyMutableStruct(ones(n));
S2 = MyImmutableStruct(ones(n));
new_A = ones(n);
@btime $S1.A .= $new_A;
@btime $S2.A .= $new_A;
1 Like

I am surprised that we can update the values in S2, as it’s an immutable type. Is this mutability just in reference to the fields the type holds, not the values in said fields? Or does this syntax have to do with BenchmarkTools perhaps?

Yes, exactly. It will always reference the address of the same vector in memory, but the elements of this vector can change

1 Like

When using " $ " in $S1.A, etc., is this akin to @view for referring to the memory values?

I think the $ is needed because the benchmak is done in global scope.
see https://github.com/JuliaCI/BenchmarkTools.jl:

If the expression you want to benchmark depends on external variables, you should use $ to “interpolate” them into the benchmark expression to avoid the problems of benchmarking with globals. Essentially, any interpolated variable $x or expression $(...) is “pre-computed” before benchmarking begins:

julia> A = rand(3,3);

julia> @btime inv($A);            # we interpolate the global variable A with $A
  1.191 μs (10 allocations: 2.31 KiB)

julia> @btime inv($(rand(3,3)));  # interpolation: the rand(3,3) call occurs before benchmarking
  1.192 μs (10 allocations: 2.31 KiB)

julia> @btime inv(rand(3,3));     # the rand(3,3) call is included in the benchmark time
  1.295 μs (11 allocations: 2.47 KiB)
2 Likes