Cache-Conscious Data Layout: Field Zoning, False Sharing, and the 128-Byte Rule
Part 1 of Low-Level Systems Design in Rust - a series on writing high-throughput, low-latency systems code, using a single-producer / single-consumer (SPSC) ring buffer as the running example.
Part 0 - Architectural Decomposition made the highest-leverage decision (remove contention structurally, so every writer gets its own ring) and holds the full series index and guiding principles.
This post starts the micro-level work: given one such SPSC ring, how should it be laid out in memory? The patterns aren't specific to ring buffers - they apply to any structure more than one core touches.
The thing nobody tells you about multi-threaded structs
When you write a single-threaded data structure, the only layout question that matters is "does it fit in cache." When you write a multi-threaded one, a second, relevant and a bit subtle question appears:
For each field, which core touches it, and how often?
Getting this wrong will make you pay in a way that no profiler flags as a single hot line. Two cores writing to different fields that happen to share a 64-byte cache line will quietly serialize each other through the hardware coherence protocol - a pathology called false sharing. The code looks lock-free. The benchmark says otherwise.
This post is about designing the layout deliberately. It comes in two parts:
Field zoning - grouping fields by (write owner, frequency) so that one core's hot writes don't evict another core's hot working set. Alignment and padding - the mechanics that make zoning real: why #[repr(C)] is load-bearing, why the magic number is often 128 and not 64, and why adding prefetch hints can make things worse.
... continue reading