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?)
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
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
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.