What is memory alignment?
Why is the memory size different even though it is the same structure?
The articles written on this blog were created with the goal of teaching. Also, the information in the article may be incorrect, so please be careful.
While coding, there are times when the size of the structure is different from what I expected. This phenomenon is officially called Memory Alignment and Padding.
When the CPU reads variables from memory, it does not read them in units of 1 byte. Because it is read in chunks, word by word (usually 4 Bytes or 8 Bytes), how variables are arranged in memory is extremely important to performance.
The overall size of the structure may vary depending on how the members of the structure are arranged in memory. This is due to the concept of padding. Padding is empty space that the compiler automatically inserts to help align the members of a structure to word boundaries on the CPU. As there is an empty space in the middle, the size of the structure naturally increases.
Let’s check it ourselves with the code.
#include <iostream>
#include <cstddef> //Using the offsetof macro
//1. Inefficient memory placement (total 24 Bytes)
struct BadOrder {
int A; //4 Bytes, the next variable requires 8 Bytes alignment, resulting in 4 Bytes of padding (using a total of 8 Bytes).
long long B; //8 Bytes, 8 Bytes used alone. (Total 16 Bytes used)
bool C; //1 Byte, 7 Bytes padding occurs after 1 Byte is used (total 24 Bytes used).
};
//2. Efficient memory placement considering compiler optimization (total 16 Bytes)
struct GoodOrder {
long long A; //8 Bytes, 8 Bytes used alone. (Total 8 Bytes used)
int B; //4 Bytes, the following variables can be used in the remaining 4 Bytes, so no padding occurs. (Total 12 Bytes used)
bool C; //1 Byte, 3 Bytes padding occurs after 1 Byte is used. (Total 16 Bytes used)
};
//3. Structure with forcibly removed padding (total 13 Bytes)
#pragma pack(push, 1)
struct PackedStruct {
int A; // 4 Byte
long long B; // 8 Byte
bool C; // 1 Byte
};
#pragma pack(pop)
A question arises here. Why is there an additional 7 bytes of padding at the end of BadOrder and 3 bytes of padding at the end of GoodOrder? This is due to alignment requirements for when structures are grouped into arrays, etc. The total size of the structure must be a multiple of the largest data type (8 Bytes of long long in the example above) among the internal members. This is because the same 8-byte alignment can be guaranteed when moving to the next element of the array.
Number 3 is to forcefully remove the padding by using #pragma pack. This can save memory to just 13 bytes, but can cause fatal performance degradation. If the CPU reads word by word and the starting point of the data is misaligned, The CPU has to read the memory twice and perform a useless additional operation to force it to be merged. In certain hardware environments, the program may even crash.
Additionally, cache line efficiency cannot be ignored. Modern CPUs usually import the entire memory in units of 64 bytes, but if it is 24 bytes as in number 1, only 2 can be stored in 64 bytes. If you optimize it to 16 Bytes like in number 2, all 4 will fit neatly. When looping an array, there is a huge difference in processing speed.
Unless there are special cases where the memory layout must be accurate without 1 byte error, such as network communication standards or file input/output, In general situations, allowing padding has greater performance benefits. Therefore, it is a good idea to get into the habit of minimizing unnecessary padding by grouping members starting with the largest size (or in reverse order), as shown in number 2.