I'd largely echo Chris' answer but with some possible deviations. The ultimate thing I'd echo wholeheartedly is to store indices to your particles and contacts, stored in big sequences like a couple of big vector pers network.
However, one part where I disagree is that you can use indices and a random-access sequence and still have constant-time removals (with a super small constant) without index invalidation if you use an indexed free list/holes strategy. If particles die, then you can remove them in constant-time and reclaim/insert in constant-time without using any extra memory, like so:
... where union Nodes nodes[n]
might be std::vector<union Node>
. It takes a little more elbow grease if Particle
or Contact
isn't trivially constructible or destructible (making a proper container in C++ for non-PODs is a bit of a PITA with placement new, aligned storage, and manual dtor invocations). Hopefully they are, as generally can be the case for this type of data.
First, what would be an efficient and community-recommended way to
store the contacts for each particle?
Assuming it's efficient to just simulate and gather the contacts for all particles at once (which is often the case in many simulations), short answer:
// Stores all the contacts between particles. Each per-particle
// contact list begins with a count followed by the indices to each
// contact.
std::vector<int> contact_data;
// Stores an index into the contact data for each particle.
std::vector<int> contacts;
To get the contacts for the nth particle, we do:
// Fetch the index into the contact data for the nth particle.
const int cd_index = contacts[n];
// Fetch the number of contacts.
const int num = contact_data[cd_index];
// Fetch a pointer to the array of contact indices.
const int* indices = contact_data.data() + cd_index + 1;
And now you have the indices of the contacts and the number of them for the nth particle. You can clear the list and compute it for each time step. You can also do it multithreaded with a bit of work (gather the contacts for ranges of particles in each thread locally, then merge the results into the two vectors at the end).
network.getContacts(Particle particle)
If you follow the suggestion to use indices, the above turns to this:
// Returns the number of contacts and a pointer to the array
// of contact indices for the particle indexed by 'particle_index'.
std::pair<int, const int*> network.getContacts(int particle_index)
... you can use something a bit nicer than std::pair
, like your own structure or class.
SmallVectors and the Like
Now using something like boost::small_vector
or llvm::SmallVector
is a very good solution, far superior to requiring a heap allocation for every particle as would be the case if you used std::vector
for each one. Chris' answer is already really good. But if you want to persistently store the contacts, these small buffer optimizations start to get a little bit explosive in memory use which can translate to extra cache misses (they are pretty great for short-lived storage though on the stack), which is why I recommend just a big old vector of integers instead with the assumption that your simulation might be better off computing all the contacts at once rather than computing them on demand in getContacts
(at which point a small vector would start to become much more appropriate).
[...] in this context is useful, as it will ensure that I am not
creating duplicate Contact objects for two particles with a shared
contact. Is there an alternative method that would make more sense?
shared_ptr
is for sharing ownership of a resource and extending its lifetime until all owners release their ownership. To just refer to a resource in multiple places without sharing ownership (which is all you need here) and without duplication of the resource's data, just use plain old indices or pointers. There's a pretty hefty relative overhead to shared_ptr
used on a per-particle or per-contact basis.
A quick thing in practice using the above techniques (including the indexed free list), and not really "particles" in this case but still agents that collide with each other and a collision list between agents (gathered all at once into two vectors of integers as shown above): 500,000 agents bouncing off of each other, single-threaded, with a good portion of the time spent just plotting pixels and not merely simulating:
First, what would be an efficient and community-recommended way to
store the contacts for each particle?
If you want to do things very efficiently when the temptation is to store a boatload of tiny lists, ideally store all the data in big sequences, not a bunch of teeny ones. You don't actually need to instantiate a million containers in order to represent the equivalent of a million containers associated to a million elements. You can just use two, for example: one storing all the data and one parallel to the million elements storing starting indices into that data.
This is a general optimization strategy regardless of context, and it addresses a common performance gotcha in languages that provide a lot of convenient containers. Those containers are very efficient for storing a million elements in one container, but not very efficient for storing a million containers with a few elements each. That applies even to the ones that use SBO (small buffer optimizations) like SmallVector
or std::string
, because the "small buffer" is either too large (at which point we're wasting memory and getting huge strides from one element to the next) or too small and incurring heap allocations. It's generally not "just right" as is the case if you use one big buffer for all the data.
Note that this answer might be a bit overkill. I'm not really accounting for productivity at all and assuming you have a bit of extra time to deal with the inconvenience of storing everything in one big container and possibly implementing that indexed free list above (I'm not a good person to ask for efficient solutions that can be implemented in 10 minutes... well, I think I can implement these in 10 minutes but only because I've had lots of practice). For VFX it's not uncommon to deal with hundreds of millions of particles, and so I'm often required to use these types of techniques and handroll containers at the bare minimum just to get acceptable performance that won't have users complaining that some other software is faster or having studios switch to simulating with something else on their farms. But I get kinda excited to share some very general techniques to speed things up, and a good one that doesn't get too fancy and hopefully isn't too overkill is just index your elements and store everything in big containers, not a boatload of small ones. I'm actually restraining myself a lot for this answer because there's a lot more I can cover on this topic including an alternative to that indexed free list for the largest scale inputs (far more complex to implement), but I'll stop here!
vector
or something like that.Network
class will ideally be flexible and allow for both cases (depending on which is the main control parameter – particle positions or contact forces). That's why I have defined both asvector
s. But in terms of having a list of contacts per particle, knowing the extra information I have provided, is my implementation outlined in my question okay (with shared pointers being replaced with regular pointers, as you suggested)? Or is more information still needed? Thanks for the continued help!if
statements to find the correct contact?