C++ Objects in memory
Objects in C++ can be mapped into memory and vice versa,
This means memory can be cast into objects:
1 | struct A |
Basic types size
It’s pretty common to assume int
is 32 bit and long
is 64 bit.
Most of us are developing on 64 bit machines so we assume:
Short
is 16 bits.Int
is 32 bits.Long
andPtr
are 64 bits.
This behavior is controlled by a standard called “Data Model” which defines what is the size of short
, int
, long
and pointer
.
We’ll assume here it’s LP64
which as stated int
is 32 bits or 4 bytes.
By simply running sizeof(type)
we can check what is the data model.
On my machine:
1 | Short 2 |
Alignment
Alignment of objects is an integer (size_t
) that represents what addresses successive object can be mapped to.
The first struct:
1 | struct A |
A
has 2 integers that are 4 bytes, which means the alignment of A is 4!
How can we confirm it? Use the alignof
operator:
1 | int main() |
Because A is aligned by 4 it means we need to place the object after 4 bytes.
Because 2 ints consists of 8 bytes the alingment is perfect!
What would happen if we add a char
?
1 | struct A |
Let’s compare both alignof
and sizeof
before the change and after:
Before:
1 | Alingment of Previous A is 4 |
After:
1 | Alingment of A is 4 |
char
is 1
byte size!
So how come the size is increased to 12
and not 9
?
This is all because alignment!
The default minimum alignment of this type is 4
therefore we need to accomodate the size.
As alignment defined - This is done to ensure successive objects can be aligned properly in memory:
What would happen if we decid to ignore this rule?
The first line is no longer aligned!
Why do we align to power of 2?
- This is how it’s built.
A bit is a binary value - 0 or 1 and types are also the power of 2 -Byte
- 8 bits,Short
- 16 bits,Int
- 32 bits,Long long
- 64 bits.
To keep it understandable and not counting offsets to uneven objects we need to write even objects.
In 3D graphics we also need power of 2 images to have them align better in memory.
- Cache lines and Pages.
A lot of performance optimization is built on the assumption that we cachePower of 2
size bytes.
Usually a cache line is 64 bytes so whenever the CPU is loading memory into its cache it will load an enitre line.
If we take the last example of structures that are not aligned in memory, imagine loading a cache line with only half the object, how would caching work when we need to loop through objects?
1 | for(auto& AObj : listOfAs) |
Padding
Padding exists to complete the alignment of classes.
Example 1
1 | struct A |
Struct A
is 12 bytes
and aligned by 4
.
Example 2
1 | struct A |
We switched the order of char
and int
does it affect the struct?
Yes and No.
We haven’t changed drastically the alignment but we did change the order, the padding is still 4.
Example 3
1 | struct A |
The size is now 24!
Let’s analyze this class:
Value
is 4 bytes long.Value2
is 1 bytes + 3 padding to align to the previous int.Value3
is 8 bytes long, Value
and Value2
both align to 8.Value4
is 4 bytes - we need to align it to 8 therefore we get another 4 bytes padding.
1 | int 4 bytes |
4 + 1 + 3 + 8 + 4 + 4 = 24
What’s the minimum alignment now?
The alignment is 8 to accommodate the long long
type.
alingas
To request to align the struct in a different manner we can use the alignas
operator:
1 | struct alignas(16) A |
Now struct A
is aligned to 16, total size is - 32 bytes.
Why do we need different alignment?
Basically it all comes down to - Performance
.
When the CPU loads cache lines or the OS load pages it want to load as much memory as it can in little effort - so when we jump through memory too much we create many misses which affect performance.
This is observed in the previous example:
Imagine our cache lines are 24 bytes - To access the 3rd object we’ll need to load 2 cache lines.
Because each line is 24 bytes and the object is aligned to 9 bytes.
To align it to 24 bytes we’ll need to hold 2 objects - 18 bytes and keep the rest of the bytes empty - 6 bytes.
What issue empty memory causes?
Fragmentation issues!
For each 2 objects we lose 6 bytes.
For 100 objects we lose 600 bytes!
That’s why classes are aligned by default.
Just make sure your classes are ordered correctly to avoid unnecessary padding!
Unordered:
1 | struct A |
1 | Alingment of A is 4 |
Ordered:
1 | struct A |
1 | Alingment of A is 4 |
When creating types be attentive to paddings,
A simple rule to go by is to order them from biggest to smallest to avoid paddings.
Thanks for reading!