CPU Cache Essentials
meta-x86.blogspot.com
meta-x86.blogspot.com
The summary gives you the content of the talk much quicker.
> For example in the next code when else clause is happening, we are throwing complete cache line which was fetched by accessing the is_alive member
struct Obj {
bool is_alive;
…
};
std::vector<Object> objs;
for (auto o: objs) {
if (o.is_alive)
do_stuff(o);
else {
// just thrown a cache line
}
}
So, how do you fix that?My first idea would be to remove the is_alive member from the objects and creating a is_alive array of boolean, one for each object. But this opens a whole new set of problems (how to map from objects to is_alive indexes, static vs dynamic set of object that can be alive, etc.).
An example would be "scene-graphs" in my game engine. Ostensibly they are stored as components (but my game engine doesn't have components) which are accessed by an unique identifier via an indirection table. Something like Bitsquid.[0]
Like Bitsquid, my "scene-graphs" can be linked to other "scene-graphs." This allows me to "glue" weapons to character's hands or armour to their torsos. This means I have "scene-graphs" that can be updated (nodes repositioned) in parallel (as they have no data dependencies) and others that cannot (linked "scene-graphs"). For performance reasons, I identify which "scene-graphs" can be updated in parallel, so I can kick them off to appropriate tasks which satisfy their data-dependencies. Thus enters the bitvector/bitset because copying my "scene-graphs" around and then adjusting other "scene-graphs" pointers (that point to the moved "scene-graph") when linking or unlinking results in a complete iteration (once if you batch) over all the "scene-graphs," or some form of indirection (some with memory fragmentation too!). Not to mention such approach complicates the code for handling "scene-graphs" a lot. Beyond that, I have to sort my "scene-graphs" (pointer list) by link depth when updating, so I don't update children before their parents, and the like. It's messy, but bitvectors/bitsets are a great solution.
I am experimenting with other methods to manage linked "scene-graphs" which fall under a split (linked/unlinked) array method, but I don't have anything yet, especially when I consider that I get a free iteration over all scene-graphs anyways (which hides a lot of the cost of the current method).
[0]: http://bitsquid.blogspot.ca/2011/09/managing-decoupling-part...
Edit: And I completely forgot to mention the performance concerns of having multiple cores touch the same memory: irregular stalls!
Simple example from the slides on optimizing for the PlayStation 3; if your object is large and full of different data, you miss a lot and burn lots of cache space every time you access it because it is so large. If your object is small and just a collection of pointers, you burn little cache space when you load it, and data can be arranged more appropriately.
http://research.scee.net/files/presentations/gcapaustralia09...
For example, in Go if I wanted have a slice of Foobar ([]Foobar) but wanted to have them allocated close to one another I could write:
const neededFoobars = 100
backingSlice := make([]Foobar, neededFoobars)
seqfoobarPtrs := make([]*Foobar, neededFoobars)
for i := 0; i < neededFoobars; i++ {
seqfoobarPtrs[i] = &backingSlice[i]
}Compound types in most languages will store their members contiguously, at least in cases where easy to do so. And arrays are generally contiguous in addition.
The problems often crop up in not being able to have compound value-types and are instead often included by reference.
Some languages, including C# IIRC, allow you to define specific types to be treated by value (structs in C#) which can often help. It may also be better to have a few arrays with primitive types in them, rather than a single array of compound types, for instance.
Make sure to use simple arrays when you want to store some data, it's not just in C but it will work well in other languages as well including javascript, php, python, perl, etc
Because that's how processors algorithms are designed.