2024-12-20 20:49:00
tigerbeetle.com
A popular data-oriented design pattern is a Struct of Arrays. Is an
Array of Enums also useful? Yes! But let’s do a refresher on struct of
arrays (SoA
) first:
If you have a Thing
,
const Thing = struct {
: u128,
checksum: u32,
number: u8,
flag };
and an array of Thing
s,
// Array of Structs.
const AoS = []Thing;
it’s possible to wrap the data inside out and use
// Struct of Arrays.
const SoA = struct {
: []u128
checksum: []u32,
number: []u8,
flag };
instead. The benefits are:
- Reduced memory usage due to amortized padding. As
flag
is a byte, it requires some padding to align with larger fields like
checksum
. InAoS
, each individual struct has
this padding. InSoA
, there’s still padding for
flag
, but it’s just one instance of padding for all the
Thing
s. - Better memory bandwidth utilization for batched code. If a loop
needs to process all things, but the processing doesn’t require all
fields (at least for the majority of objects), then an array-based
representation reduces the amount of data that needs to be loaded.
You can use the same trick also for a more SIMD-friendly data layout.
A bad way to use SIMD is to try to vectorize the processing of an
individual item. A good way to use SIMD is to process several items at
once.
For example, if a Thing
in question is a 3D vector, it’s
usually not a good idea to implement the dot product of two vectors by
loading xyz
of one vector into a SIMD register
a
, xyz
of another vector into a SIMD register
b
, and then performing a SIMD multiplication of
a * b
. The reason is that your “SIMD width” is still only
three numbers:
Much better to simultaneously compute the dot products of multiple
pairs of vectors. Then, you can load a SIMD vector with
xxxxxxx
, which is the x
coordinate for the
first vector of multiple pairs. And so, the full width of SIMD registers
is used:
This post is to raise awareness that the same trick works with
enums:
const Thing = union(enum) {
: Spam,
spam: Eggs
eggs
}
// With a level of abstraction peeled:
const Thing = struct {
: u8,
tag: union {
payload: Spam,
spam: Eggs,
eggs
} };
Here’s an array of enums:
And here’s the dual, an enum of arrays:
const EoA = struct {
: u8, // Sic!
tag: union {
payload: []Spam,
spam: []Eggs,
eggs
} }
The key idea is that there’s only a single tag
per an entire batch of things. If a batch size is 1024, you can have
1024 spams or 1024 eggs, but not a mix of the two.
The benefits of this representation:
-
Tags occupy less bytes in total, because a single tag can be
amortized across many objects at once. -
If individual enum variants are of different sizes, then no
memory is wasted when storing the gap between a small variant and a
large one. -
Finally, the purpose of the tag is for the CPU to branch on it.
By having only one tag per batch of objects, the amount of branching the
CPU has to do is dramatically reduced, which is a very good thing in
itself—unpredictable branching slows down the CPU a lot. But what’s
more, with branching out of the way, the processing is opened up for
SIMD optimizations!
TigerBeetle is built around this idea of AoE => EoA
optimizations. TigerBeetle can do two things: create new accounts, and
then create transfers between accounts. This could have been
represented as a heterogeneous enum of the two kinds of operation.
Instead, TigerBeetle uses homogeneous batches. TigerBeetle does eight
thousands operations at a time, and there’s one tag for an entire batch.
So, it’s always 8k transfers followed by 8k accounts, not a mixture of
the two.
This optimization is related to the concept of data plane and control
plane separation. The tag
is control plane, a small,
infrequent piece of information which informs decisions. The data plane
then is more voluminous, but processing it doesn’t require many
individual decisions. Just. One. Branch!
That’s all for today. Support your local branch predictor, convert
your AoE to EoA!
Keep your files stored safely and securely with the SanDisk 2TB Extreme Portable SSD. With over 69,505 ratings and an impressive 4.6 out of 5 stars, this product has been purchased over 8K+ times in the past month. At only $129.99, this Amazon’s Choice product is a must-have for secure file storage.
Help keep private content private with the included password protection featuring 256-bit AES hardware encryption. Order now for just $129.99 on Amazon!
Support Techcratic
If you find value in Techcratic’s insights and articles, consider supporting us with Bitcoin. Your support helps me, as a solo operator, continue delivering high-quality content while managing all the technical aspects, from server maintenance to blog writing, future updates, and improvements. Support Innovation! Thank you.
Bitcoin Address:
bc1qlszw7elx2qahjwvaryh0tkgg8y68enw30gpvge
Please verify this address before sending funds.
Bitcoin QR Code
Simply scan the QR code below to support Techcratic.
Please read the Privacy and Security Disclaimer on how Techcratic handles your support.
Disclaimer: As an Amazon Associate, Techcratic may earn from qualifying purchases.