How misaligning data can increase performance 12x by reducing cache misses
danluu.com
danluu.com
It's easy, from a software engineer perspective, to know that your CPU has cache, and just think it's like any other cache you might implement in software. But the implementation details - the hashing by masking the address, and only having a few slots available for things that hash to the same bucket - are actually important, as shown here.
Some architectures do not support unaligned memory access and will raise an exception. If you're using things like packed attribute with your structs your compiler will generate the correct code but that code will be slower. In almost all cases it will generate many more instructions and because of that your cache will be less effective (due to larger code size) your decoder cache will be less effective, etc...
The author has a more modern Intel processor. The x86 family always supported unaligned access, albeit it was always slower in terms of cycles. More recent Intel processor have made this penalty much shorter. I believe this was driven network applications many of which focus on efficiency of packing as many bytes down the channel and less on the alignment requirements. http://www.agner.org/optimize/blog/read.php?i=142&v=t
This optimization doesn't preclude those architectures necessarily, it's saying that instead of allocating at address 512, 1024, etc., there might be a boost from allocating at off-page addresses.
As a not-systems c++ programmer, it seems to reinforce the usual lesson: don't optimize your structures initially for anything but readability, once you have your system running and can pinpoint the bottlenecks, then try things like aligning structures to various things. But naturally always have a real-world-like test-suite to verify you are improving things.
Sorry if this is boring.
Compilers are actually built to align the structures you write as there are a lot of processors which have significantly slower access to the misaligned values. Allocators also have to return you aligned addresses for each "malloc." The newest Intel iX processors are actually an exception in being able to amortize misaligned accesses.
I've actually used hand-made unaligned "string" stores. As soon as you allocate some bigger memory block and store the character sequences of the variable size one after another, their starts won't be aligned unless you want that. For doubles and other fixed-size values it's still better to keep them aligned.
Moreover I wasn't able to extract some value out of the article. Something constructed can be constructed to be slow or fast, fine. But I don't see anything that would inspire me to improve my real code. Maybe somebody who reads the article manages to produce such examples?
[EDIT] although it is tricky to do optimally given that different processors will have different cache set characteristics (as the article shows).
And most of the programmers make much bigger omissions than those mentioned in this topic. Like using wrong algorithms, wrong libraries, doing too many allocations, having bad structures of the data... So this topic effects are invisible unless you already fixed other issues.
If your structures happen to be very close to the system's page size it could easily happen, then you'd need to avoid this yourself with your own incremental allocator (or other tricks).
Definitely agree with your last point, I've seen lots of code (in commercial applications) where people are optimising completely the wrong thing.
Having said all that, before you do this. Why don't you first measure where you're seeing high cache misses and only optimize those. On Linux the perf tool is a god send, you can annotate down to the source code line.
Great example. I looked briefly at the source, and wasn't sure whether "pointer_chase" was on or off in your graphs. Or maybe it didn't make a difference?
Page-aligned accesses like these also make compulsory misses worse, because prefetchers won’t prefetch beyond a page boundary. But if you have enough data that you’re aligning things to page boundaries, you probably can’t do much about that anyway.
To the contrary, I think this is one of the relatively rare cases that explicit prefetching can help you. But maybe this helps only once your sets are too large for L3?
I wrote a little a few months ago on my attempts to speed up a Stream benchmark for Sandy Bridge that might have some overlap with your post: http://software.intel.com/en-us/forums/topic/480004#comment-...
This was a great explanation of caching architecture, but I really want to hear the story of how the OP automated himself out of his first job with some shells scripts and Ruby.
To avoid them in code, it's often sufficient to round to convenient decimal numbers when allocating arrays, instead of powers-of-2. That is, allocate an array of 1000, instead of an array of 1024.
The actual discussion is good, too.
Either avoid walking the elements, or do whatever it takes to ensure that you don't end up getting stuck behind the N.
Hardware is inescapable.
It's also worth noting that you can use this to your advantage, ensuring that accesses to certain elements does not push much else out of the associative cache.
You can see an example of this technique here: https://github.com/scotts/streamflow/blob/master/streamflow....
This bit of code executes when a call to malloc() could not find any free memory, and it has to request more memory from the operating system. The function supermap() allocates a large chunk of memory from the kernel. We then write the meta-information to keep track of that chunk of memory directly to itself - that "meta-information" is the pageblock_t struct. In malloc(), we then use that chunk of memory to find a sub-chunk inside of it to return to the user.
Note that internal to malloc(), we will maintain lists of these pageblock_t structures, and they will all be page-aligned. So, anytime we want to search for free memory among the managed pages, we're going to access page-aligned structures.
Took me a few moments to grok the charts too, since I haven't had my coffee yet.
It also helped when I realized the graph starts at 1 for working set size of 8 and the author corresponding says:
>Except for very small working sets (1-8), the unaligned version is noticeably faster