Course contentsShow
← Rust
Lesson 1727 of 1,91015. Performance and Architectural EngineeringFree lesson

Data Alignment for Cache Efficiency

Force memory alignment to avoid split-cache-line accesses.

In this lesson, you will learn how to manually align data structures to ensure that high-frequency objects reside within a single cache line, preventing performance-degrading split-access penalties.

The Cost of Split-Cache-Line Accesses

Modern CPUs fetch data from main memory into L1 caches in fixed-size chunks, typically 64 bytes. When a data structure straddles the boundary between two cache lines, a single memory access requires the CPU to fetch two distinct lines, merge them, and then perform the operation. This "split access" effectively doubles your latency for that memory load.

By default, Rust’s compiler aligns types based on their largest field to ensure natural alignment. However, for performance-critical systems, you may need to force a specific alignment to ensure your hot data structures start at the beginning of a cache line, or to guarantee they occupy exactly one cache line.

Controlling Alignment with #[repr(align(n))]

To force alignment, we use the #[repr(align(n))] attribute. If you are building a high-performance system where a specific struct must not straddle a cache boundary, you can align it to 64 bytes (the common x86/ARM cache line size).

#[repr(align(64))]
struct HotData {
    pub counter: u64,
    pub state: [u32; 14], // Total size: 64 bytes
}

In the example above, the #[repr(align(64))] forces the compiler to start the HotData struct at a memory address that is a multiple of 64. Because the struct size is exactly 64 bytes, every instance of HotData is guaranteed to fit perfectly within a single cache line. This layout eliminates the risk of a split access, as the structure will never overlap into an adjacent line.

When to Use Alignment

While alignment is powerful, it carries a cost: padding. If you align a structure to 64 bytes but the data only takes 8 bytes, you waste 56 bytes per instance. This decreases your overall cache density, potentially causing more cache misses by pushing other useful data out of the cache. Only use this technique for "hot" structures that are accessed frequently in tight loops where the penalty of split accesses outweighs the loss of spatial density.

Key Takeaway: Use #[repr(align(64))] to force hot, cache-sensitive structures to start on cache-line boundaries, ensuring single-cycle access and preventing the latency overhead of split-cache-line fetches.