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

False Sharing in Atomic Variables

Pad concurrent data structures to ensure independent variables occupy distinct cache lines.

In this lesson, you will learn how to prevent performance degradation in highly concurrent systems by padding atomic variables to align with CPU cache lines.

The Conflict of Cache Coherency

Modern CPUs manage memory in chunks called "cache lines" (typically 64 bytes). When you use an AtomicU64 in a multi-threaded application, the hardware ensures that if one core modifies that value, all other cores see the update. This is achieved via the MESI (Modified, Exclusive, Shared, Invalid) protocol.

"False sharing" occurs when two independent Atomic variables—meant to be accessed by different threads—reside on the same cache line. Even though the threads are modifying different variables, the CPU perceives a conflict on the shared cache line. Every write forces the hardware to invalidate the line across all cores, causing a "ping-pong" effect where cores fight for ownership of the cache line, obliterating performance.

Padding for Isolation

To resolve this, we force these variables onto separate cache lines using manual padding. By inserting a "spacer" of bytes between atomic variables, we ensure that the memory addresses are far enough apart that they cannot reside on the same cache line.

use std::sync::atomic::AtomicU64;

#[repr(align(64))]
pub struct PaddedAtomic {
    value: AtomicU64,
    // The alignment attribute and padding force this 
    // struct to occupy an entire cache line,
    // preventing neighboring data from interfering.
    _padding: [u64; 7], 
}

In the example above, #[repr(align(64))] ensures the struct starts on a 64-byte boundary. By adding the _padding array, we consume the remainder of the cache line. Now, when multiple PaddedAtomic instances exist in a struct or array, each resides in its own isolated cache line.

This technique is essential when building high-throughput primitives like MPMC (Multi-Producer, Multi-Consumer) queues or global counters, where uncontended access is a performance requirement. Without this isolation, your atomic operations would suffer from silent, hardware-level serialization, negating the benefits of parallel execution.

Key Takeaway: To eliminate false sharing, use #[repr(align(64))] and manual byte-padding to ensure that frequently updated atomic variables occupy distinct cache lines, preventing unnecessary cache-coherency traffic between cores.