C Struct Memory Alignment and 64-Bit Performance
In C on modern 64-bit architectures, memory alignment dictates how data is arranged and accessed in physical memory. When struct members are not naturally aligned to memory boundaries matching their data types, the compiler inserts implicit padding bytes, increasing the memory footprint of the structure. This article explores how memory alignment works in 64-bit computing, how struct member ordering causes padding, and how these factors directly affect CPU cache utilization, memory bandwidth, and execution speed.
Understanding Memory Alignment on 64-Bit Systems
Modern 64-bit CPUs access memory in chunks of 64 bits (8 bytes) at a time across the memory bus. To maximize hardware efficiency, data types must reside at memory addresses that are integer multiples of their own size. This is known as natural alignment:
char(1 byte): Can reside at any memory address.short(2 bytes): Must reside at an address divisible by 2.int,float(4 bytes): Must reside at an address divisible by 4.long,double, pointers (8 bytes): Must reside at an address divisible by 8.
The total size of a struct must also be a multiple of the size of its largest member to ensure alignment in an array.
Struct Padding and Member Ordering
If a developer declares struct members without considering alignment, the compiler automatically inserts padding bytes between members and at the end of the struct.
Consider this inefficient struct:
struct Inefficient {
char a; // 1 byte
// 3 bytes of padding
int b; // 4 bytes
char c; // 1 byte
// 7 bytes of padding
double d; // 8 bytes
}; // Total: 24 bytesThe fields only account for 14 bytes of data, yet the struct consumes 24 bytes due to 10 bytes of padding.
By reordering members from largest to smallest, the padding is eliminated:
struct Efficient {
double d; // 8 bytes
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte
// 2 bytes of padding (to reach an 8-byte multiple)
}; // Total: 16 bytesThe optimized struct uses 33% less memory while retaining the exact same data fields.
How Alignment Directly Affects Performance
1. CPU Cache Line Utilization
Modern CPUs retrieve memory through hierarchical caches (L1, L2, L3) that move data in fixed blocks, typically 64 bytes per cache line.
- Inefficient Struct (24 bytes): Only two full instances can fit into a single 64-byte cache line, wasting significant cache capacity on dead padding space.
- Efficient Struct (16 bytes): Exactly four full instances fit into a 64-byte cache line.
Packing structs effectively increases data density in the cache. In loops iterating over millions of structs, higher density leads to fewer cache misses, fewer trips to main RAM, and massive speed improvements.
2. Misaligned Memory Access Penalties
If alignment is disabled using compiler directives like
#pragma pack(1) or __attribute__((packed)),
the compiler eliminates all padding. However, this causes values to
cross natural address boundaries and even cache line boundaries.
When a 64-bit value spans two separate 64-byte cache lines, the processor must execute two memory reads, mask the unnecessary bits, and shift the remaining bits together to assemble the value. On x86-64 architectures, this causes a severe CPU cycle penalty. On certain ARM64 or RISC-V architectures, misaligned access can trigger a hardware exception that must be resolved by the operating system kernel, degrading performance by several orders of magnitude.
3. Vectorization and SIMD Operations
Modern 64-bit architectures rely heavily on Single Instruction, Multiple Data (SIMD) extensions like AVX-512 or ARM NEON to process data in parallel. SIMD instructions often require 16-, 32-, or 64-byte aligned boundaries. When processing arrays of structs, misaligned or unpredictably padded data blocks auto-vectorization, forcing the compiler to generate slower scalar instructions instead.
Optimization Strategies
To ensure optimal performance on 64-bit systems:
- Sort Struct Members by Size: Arrange fields in descending order of size (8-byte types first, followed by 4-byte, 2-byte, and 1-byte types) to naturally minimize compiler padding.
- Avoid Global Packing: Do not use
#pragma pack(1)for structures processed in performance-critical loops. Reserve packed structs strictly for parsing raw network packets or hardware binary formats. - Use Explicit Alignment When Necessary: Use
alignas(introduced in C11) or compiler attributes like__attribute__((aligned(64)))to align high-throughput structs directly to cache line boundaries, preventing false sharing in multithreaded environments.