Skip to main content

False Sharing

False sharing puts independent variables written by different cores on one cache line, so coherency traffic ping-pongs the line and quietly wrecks multicore scaling. Pad or align hot variables to separate cache lines, or use thread-local state and merge at the end.

False sharing is a low-level performance defect where two or more independent variables, written by different threads, happen to reside on the same CPU cache line (typically 64 bytes). Even though the threads never touch the same variable, the cache-coherency protocol works at cache-line granularity, so each write invalidates the line in the other cores' caches. The cores end up bouncing ownership of the line back and forth — "ping-pong" — adding huge latency to what looks like contention-free code.

Why It Happens

It arises from memory layout, not logic. Hot counters or per-thread accumulators packed into adjacent array slots or struct fields land on one line. A common case: an array indexed by thread ID where each thread updates its own slot — logically independent, physically adjacent. Developers reason about correctness (no shared variable, no race) and never about physical layout, so the problem is invisible in the source. It is also invisible in single-threaded tests; it only appears when multiple cores write concurrently.

Why It Hurts

Each false-sharing write can cost dozens to hundreds of cycles instead of a few, because the line must be re-fetched across the coherency fabric every time. Throughput can drop several-fold, and — counterintuitively — adding cores makes it worse, since more cores fight over the same line. Because there is no lock and no logical contention, the slowdown is baffling: the code is "obviously" parallel yet refuses to scale.

Warning Signs

  • Adjacent struct fields or array elements written by different threads.
  • Parallel code that scales poorly or degrades as cores increase, with no locks involved.
  • Profilers/PMU counters showing high cache-coherency or cross-core invalidation misses.
  • Per-thread state stored in a small contiguous array.

Better Alternatives

  • Cache-line padding / alignment: pad each hot variable so it occupies its own line (@Contended in Java, alignas(64) / padding in C/C++, CachePadded in Rust).
  • Thread-local storage: keep per-thread state truly local and combine results at the end.
  • Restructure data layout so concurrently written fields are far apart (array-of-padded-structs, or separate arrays).
  • Reduce write frequency to shared lines (batch updates, accumulate locally then publish).

How to Refactor Out of It

Confirm with hardware performance counters that cache-coherency misses are high. Identify which independently written variables share a line — usually adjacent counters or per-thread slots. Separate them: pad each to a full cache line or align hot fields, or move per-thread data into thread-local storage and merge at the end. For per-thread arrays, switch to an array of padded structures so each thread's slot sits on its own line. Re-benchmark across core counts; correct code should now scale up rather than down. Pad judiciously — over-padding wastes memory and cache capacity.