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

Minimizing Atomic Contention

Apply batching to atomic operations to reduce pressure on the global cache-coherency bus.

In this lesson, you will learn how to reduce overhead on the global cache-coherency bus by batching atomic operations instead of performing them individually.

The Cost of Atomic Contention

When multiple CPU cores attempt to modify the same atomic variable simultaneously, the hardware must coordinate via the cache-coherency protocol (like MESI). Every fetch_add or compare_exchange forces the cache line containing that variable to bounce between CPU caches. If you have a high-throughput system where many threads perform these updates at once, the cost of synchronizing the cache lines across the bus becomes a performance bottleneck, creating "cache line bouncing" that stalls your pipeline.

Batching for Throughput

Instead of letting every thread contest for the global atomic, you can employ batching. By having threads accumulate updates locally and only applying them to the global atomic variable occasionally, you drastically reduce the frequency of synchronization events.

Consider a performance counter. Instead of every increment triggering an atomic operation, threads can maintain a thread-local buffer or a simple private variable:

use std::sync::atomic::{AtomicU64, Ordering};
use std::cell::Cell;

thread_local! {
    static LOCAL_COUNT: Cell<u64> = Cell::new(0);
}

fn increment_batched(global: &AtomicU64, threshold: u64) {
    let current = LOCAL_COUNT.with(|c| c.get() + 1);
    
    if current >= threshold {
        // Flush the batch to the global atomic
        global.fetch_add(current, Ordering::Relaxed);
        LOCAL_COUNT.with(|c| c.set(0));
    } else {
        LOCAL_COUNT.with(|c| c.set(current));
    }
}

Why This Works

By buffering updates, you transform a stream of high-contention operations into infrequent, bulk updates. While this introduces a small amount of "stale" data (the global counter is not perfectly real-time), it massively increases the throughput of the system. The cache-coherency bus is no longer saturated with constant invalidation requests, allowing the hardware to spend more time executing actual logic rather than managing memory state transitions between cores.

Key Takeaway: To minimize atomic contention, shift from fine-grained synchronization to local batching, amortizing the cost of cache-line synchronization over multiple updates.