C++ encapsulation for Data-Oriented Design
bannalia.blogspot.com
bannalia.blogspot.com
- Straight up, this isn't data oriented. You're dealing with one object at a time.
- This code is vastly harder to understand in its entirety, much less extend, than the data oriented approach. This is template craziness to it's fullest degree, and what it's doing is going to be unfamiliar to the vast majority of programmers. Hell, I've read an article, and have been staring at for 15 minutes now and I'm not fully sure. Admittedly, I'm a bit rusty with my templates, but that's a very bad sign for maintainability.
- Memory access patterns aren't the only reason to do DOD, a big one is being able to easily simd optimize your code. This defeats that.
- If you're not going to simd optimize your code, then storing everything as SoA is often not a great idea (you want to group the things you touch at the same time, so that you have fewer cache misses).
- You can still have encapsulation with data oriented code. It's just encapsulated at a different granularity (you encapsulate access to the data arrays).
In theory, this should help the compiler automatically vectorize (than AOS), but even if it doesn't it still allows you to use a slightly higher-level interface for loading your values for manual SIMD.
Note that at the end, the poster says
> Traversal is at the core of DOD, as the paradigm is oriented towards handling large numbers of like objects. In a later entry we will extend this framework to provide for easy object traversal and measure the resulting performance as compared with OOP.
I also agree that recursive templates are a pain, but "modern" C++ kind'a expects you to use them anyway. As recursive templates go, this one isn't all that bad to use.
http://bitsquid.blogspot.com/2014/08/building-data-oriented-...
http://bitsquid.blogspot.com/2014/09/building-data-oriented-...
http://bitsquid.blogspot.com/2014/10/building-data-oriented-...
http://bitsquid.blogspot.com/2014/10/building-data-oriented-...
Thanks for the Bitsquid links.
Most of the content is in video form so not easy to grok, but there is essentially a way to switch from Array of Structs to Struct of Arrays with a single keyword. This gets you many of the benefits of Data-Oriented Programming with minimal effort.
I had to recompile it and duplicate large parts of the library code just because somebody put private, protected or sealed for no apparent reason in few places.
In JS keeping classes only as local variables not exposed in any way to user of the library has the same effect. Please don't do it. If you want me not to shoot myself in the foot then create good api, not just try to hide the gun in the closet. I'll find it anyway and the more time I spend looking for it, the less time I'll have to understand how to operate it safely.
Then compiler folks reverse-engineer all that to get "really good" performance. But they can't make one code for every model of CPU because this stuff changes with each release.
How about, CPU leaves these decisions up to the compiler? Flatten and simplify, and make (allow) compiler writers to know exactly what's going on, and even control it!
And hardware doesn't do it well at all, at least not in the super-optimization cases discussed in the OP. They're working hard to reverse-engineer around the hardware; which is at least as hard as the compiler programming caches and pipelines explicitely.
1) It doesn't exist. Theory is irrelevant, build an actual chip and run some actual software on it and then let's talk.
2) VLIW of 33 operations? Ain't no way in hell they are keeping that fed. Not happening. AMD couldn't keep a VLIW of 5 fed on their GPUs. They dropped to VLIW4, then switched to a RISC SIMD approach. And that's with a compiler that only has to deal with a very constrained grammar that almost never encounters branches. Which is about as far from general purpose workloads as you can get.
Issue #2 is less of a reason to worry. The 33 instructions is an upper limit, and will likely be a lot lower on most implementations. They also don't expect, nor need, it to be saturated.
Further, their instruction set is much more amiable to parallelism than normal instruction sets because
* Their instructions have special values (NaN and NaR) that enable extra parallelism.
* Their instructions avoid side effects that prevent parallelism. This includes not just condition codes, but effectively the whole belt mechanism.
* Their instructions can move data across themselves horizontally (under certain phase boundaries), so certain data dependencies are allowed inside an instruction. In essence, you're running at three-to-five times the clock rate but each clock is only allowed to run a given subset of instructions.
There's an example they give where the whole of a vectorized strcpy iteration is done in one instruction, with no set-up or tear-down needed. This is of course a best-case scenario, in that every technique they have is in use simultaneously, but all of the techniques they give are generally applicable and (relatively speaking) simple for compilers to use.
The compiler already does this. Traditional architectures then require you to use these results to make a register mapping, which is then basically (kind-of) undone in hardware with register renaming.
The Mill (kind'a) just doesn't do these two steps.
> And hardware doesn't do it well at all
It really does, though. Compiler-aided prefetch, even if magically perfect, wouldn't even help here since the problem is in the data layout.
Compilers are not reverse-engineering the CPU, and different CPUs do not behave that radically different to make this non-portable.
The biggest issue by far are the languages themselves. Java, for example, is inherently anti-CPU-friendly by design. You can't get flattened objects, almost everything is done via reference instead of value, etc... This was done for other tradeoffs, but the majority of languages in use were not built for raw CPU throughput.