Can we hide the actual memory layout without big overhead using C++ inline/template functions/classes? Would that be the visitor pattern?
Can we hide the actual memory layout without big overhead using C++ inline/template functions/classes? Would that be the visitor pattern?
I haven't read the whole article, but this "make copies of elements from an array into another array for the current frame only" is common in game development.
Remember that on modern CPUs, an L3 miss is about 200x slower than an L1 hit. RAM isn't random access: randomly jumping around is slow, but iterating over an array is fast, both because of the cache and because of pre-fetching.
Say you have a big array of A's, and another big array of B's. For the current frame, some of the A's need to interact with some of the B's. If you go through the entire list of B's, and copy the ones that will definitely need to interact into a new list, call it B2, then maybe (or not) do the same with the A's into A2, then it can often be approximately 30 times faster. Multiply that by 4 (or 8) if you can "zip" through your A2's and B2's with SIMD.
Not only that, but your A2 and B2 lists can be put on a stack allocator (nothing to do with allocating on the stack - it's a special type of O(1) heap allocator whose contents are discarded at the end of each video frame).
If you need to copy only every N-th byte from a AoS it might be as inefficient as random access. So, copying could be expensive.
The article suggests striping your data in blocks but then you end up with the worst of both worlds in terms of program code complexity.
This seems to claim to do it: https://github.com/crosetto/SoAvsAoS
Found that while looking for this, which I vaguely knew about and which also seems to do that: https://m-sp.org/downloads/cgo2018-src-poster.pdf
The author's advice is that you should go with the standard array-of-structures (AOS) format by default, but if you know you'll be doing number crunching, use an "unrolled by eight" grouped SOA format that's both SIMD- and cache-friendly.
I try a lot to make a "array of structs" and also"structure of arrays" for my own little relational language in rust.
Is just not possible (that I know). At best, you could store as packed arrays or arrays of arrays then at runtime static dispatch them.
P.D: Or generate code for both. Anyway is not easy to build... the OPTIMAL algorithms for both cases diverge enough.
Not super easily because the array type needs to know the fields of the class it's containing to do the re-write. This is where you need more substantial codegen to enter the picture. Something like the metaclasses proposal should handle it just fine. Or macros in the meantime.
But yes, better reflection is needed to make it truly generic.
The way I see it to store your data in whatever is the most efficient form for your computations to use, and use a simple view for those functions. Then for functions which need to look at the data in another form you use more complicated views which can abstract some of the data layout for you and make it simpler to manipulate.
Unfortunately I can see some people decrying this sort of code as too complex and complicated, but I think it can be made to work rather well.
Other algorithms do some kind of random access to a few fields only and they don't benefit at all. Those algorithms can make up 90% of your code but only account for 10% of the computation. Therefore it would be easier to have your data look like a AoS in 90% of your code but actually be stored as a SoA to gain the speed in 90% of the computation.
If, for example you've got a vector of structs (which is a basic tabular store, that is row major). Depending on the operations you're performing, you may see huge performance benefits from instead using a column oriented data structure. Especially with very large datasets. A large part of this because of cache locality and prefetch.
I see this in finance often. For querying large, slowly changing datasets, column store RDBMS destroy traditional row oriented stores. Column stores can be colloquially an order of magnitude faster for some operations, such as computing aggregates grouped by a date (but theyre significantly much slower for inserts and even more so for updates).
As usual, when it comes down to optimizations, depends on the use case, and experiment and measure, measure, measure.
Also, another big caveate is that it can change arbitrarily with different hardware or even OS revisions.
Edit: spelling
(One problem with Jai is that unless you are viewing his Youtube videos regularly, you cannot catch up on what is going on with the language...)