Reducing allocations

The Atoms type seems to be an example of something I often have in my codes, i.e. a struct representing the data and the state of the whole model. Most fields are vectors that are obviously mutable (or, rather, their contents are), but some fields are scalars or SVectors/SMatrices that are not mutable. It is natural to make them mutable as well and I don’t think it is a problem. A straightforward way is to make the whole struct mutable. Mutable types are not automatically less performant than immutable. Mutability prevents certain types of optimizations and vectors of mutable types are not stored in memory the same way as immutable types, but in this case I would first check if there’s any performance difference if you make Atoms mutable. Another way I’ve sometimes used is to use Ref so that they are stored as references and can be changed (and your refer to them as atoms.lvecs[]). A third way is to use Accessors.jl.

I guess I don’t see the benefit of using a mutable struct if I can make the struct’s fields mutable. (e.g.atoms.positions is mutable, why does atoms need to be mutable?)

1 Like

Depending on the computations, what might be a significant improvement is to store the coordinates, for example, of all atoms, as a vector of SVectors.

(For instance considering the StructArrays.jl package)

But all these considerations should worry you after eliminating the allocations in critical parts of the code, using mutable data or not. Then, further optimizations should be very focused and based on profiling.

Another thing is that you can use MMatrix as intermediates for conveniently “updating” an SMatrix, if the function does not escape the mutable object:

julia> function modify_smatrix(S)
           M = MMatrix(S)
           for _ in 1:1000
               for j in 1:3, i in 1:3
                   M[i,j] = rand()  
               end
           end
           return SMatrix(M)
       end
modify_smatrix (generic function with 1 method)

julia> @btime modify_smatrix!($(rand(SMatrix{3,3,Float64})))
  6.476 μs (0 allocations: 0 bytes)
3×3 SMatrix{3, 3, Float64, 9} with indices SOneTo(3)×SOneTo(3):
 0.288959   0.527593   0.839765
 0.556196   0.0926586  0.96533
 0.0807712  0.800752   0.775332
1 Like

I am doing this.

struct atoms
    title::String
    latpar::Float64
    lVecs::MMatrix{3,3,Float64,9}
    coordSys::Vector{String} # 'D' for Direct or 'C' for cartesian
    positions:: Vector{SVector{3,Float64}}  # List of all the atomic basis vector separated by type 
    velocities:: Vector{SVector{3,Float64}} # Velocities of the atoms
    masses:: Vector{Float64}
    atomTypes:: Vector{Int64} # Integers representing the type for each basis atom
    species:: Vector{String}  # Species of the atoms 
    energies::MVector{2,Float64}
    lj_vec::Vector{Float64} # Vector of 1/r^6 and 1/r^12 values for each unique pair of atom types
end
2 Likes

Well, I prefer to use MMatrix as default and only switch to SMatrix when I cannot get rid of allocations otherwise. For me, MMatrix and MVector are good enough to avoid allocations in most cases.

1 Like