Hi all,
I’m working on a piece of code where I have triangles which are made of points. Each point has a number of properties such as position, velocity, force…
In some part of the code I need to calculate the force for each triangle, and assign it to the triangle points.
I have a Triangle struct, a Point struct and I make use of the StructArray package.
For this, I have coded the following MWE:
using StaticArrays
using StructArrays
# This allows me to have a "view" on the points
# Note: it is similar to the LazyRow type but with a minor tweak to allow three indexes
struct StructArrayView{T,Ti}
structarray::T
idxs::Ti
end
import Base.getproperty, Base.setproperty!
# I need to overload these methods to access the points in the triangle and write the force to them
Base.@propagate_inbounds function Base.getproperty(c::StructArrayView, s::Symbol)
return getproperty(getfield(c, 1), s)[getfield(c, 2)]
end
Base.@propagate_inbounds function Base.setproperty!(c::StructArrayView, s::Symbol, val)
getproperty(getfield(c, 1), s)[getfield(c, 2)] = val
return
end
struct Point{T}
idx::Int
position::SVector{3,T}
velocity::SVector{3,T}
mass::T
force::SVector{3,T}
end
struct Triangle{T, Tpoints}
idx::Int
points::StructArrayView{Tpoints, SVector{3,Int}} # To have a view on the points contained in the triangle
rho::T
dt::T
end
npoints = 10000
ntris = 10000
points = StructArray(Point{Float64}(i, rand(3), zeros(3), 1., zeros(3)) for i in 1:npoints)
tris = StructArray(Triangle(i, StructArrayView(points, SVector{3}(rand(1:npoints) for _ in 1:3)), 2.0, 3.0) for i in 1:ntris)
# Dummy force calculation
function force!(triangles)
for t in triangles
# The custom StructArrayView allows to get the positions like this...
pos = t.points.position
val = SVector{3}(pos[i] for i in 1:3)
# ... and to write the forces like this
t.points.force -= val
end
end
force!(tris)
# Profiling in VSCode... notice the allocations in setproperty!
@profview [force!(tris) for _ in 1:100]
When profiling the code, I get some allocations in the overloaded setproperty!. I don’t understand these allocations (everything should be type stable and inferrable by the compiler). What is even more intriguing is if I manually inline the setproperty! by including it in the force! function:
function force!(triangles)
for t in triangles
pos = t.points.position
val = SVector{3}(pos[i] for i in 1:3)
getproperty(getfield(t.points, 1), :force)[getfield(t.points, 2)] -= val
end
end
The allocations magically disappear!
I’ve been banging my head on this, can someone help? What is causing these allocations?
Thanks!