# False sharing **False sharing** is a performance bug where two threads modify logically unrelated variables that happen to sit on the same [[cache]] line, causing the [[cache-coherence]] protocol to bounce the line back and forth between their caches as if they were actually contending for the same data. Nothing is functionally wrong: each thread only ever reads and writes its own variable. The slowdown is purely an artifact of cache-line granularity. ```c struct counters { atomic_int a; // written by thread 1 atomic_int b; // written by thread 2, but shares a line with a! }; ``` If `a` and `b` fall within the same 64-byte line, a write to `a` invalidates thread 2's cached copy of the whole line (including `b`) under [[mesi]]-style protocols, even though thread 2 never touches `a`. Thread 2's next read or write to `b` then misses and has to fetch the line again, and the pattern repeats every time either thread writes, turning what should be two independent, uncontended operations into a stream of coherence traffic. ## Diagnosing it False sharing is invisible in the source code, since there's no data race and no incorrect output, only a mysterious slowdown that gets worse with more threads instead of better. It typically shows up as unexpectedly high cache-miss or coherence-traffic counters in a hardware profiler (`perf c2c` on Linux is built specifically to surface this: it identifies cache lines with high cross-core contention and maps them back to the offending struct fields). ## Fixing it The standard fix is **padding**: inserting unused bytes between fields so that variables written by different threads land on different cache lines. ```c struct counters { atomic_int a; char pad[60]; // push b onto the next 64-byte line atomic_int b; }; ``` C++17's `std::hardware_destructive_interference_size` and similar constants in other languages exist to make this padding portable across architectures with different line sizes, rather than hardcoding 64 everywhere. The tradeoff is memory: padding a small, frequently-allocated struct to cache-line boundaries can noticeably inflate its footprint, which matters when many instances exist (per-thread counters in a large thread pool, for example) even though it's a non-issue for a handful of long-lived globals.