False Sharing Cache
False Sharing is the insidious parallel performance pathology where two or more threads on different cores modify independent variables that happen to reside on the same cache line — causing the hardware cache coherence protocol to repeatedly invalidate and reload the entire cache line across cores, creating catastrophic cache line "ping-pong" that can slow down parallel code by 10-100x despite the threads sharing no logical data.
How False Sharing Occurs
CPU caches operate on cache lines (typically 64 bytes). When Thread 0 on Core 0 writes variable A and Thread 1 on Core 1 writes variable B, and A and B are within the same 64-byte cache line, the coherence protocol (MESI/MOESI) invalidates Core 1's copy when Core 0 writes, and vice versa. Each write forces the other core to fetch the updated cache line from the L3 cache or the writing core's L1 — a round trip of 40-100 cycles per access instead of 3-4 cycles for a local L1 hit.
Classic Example
int counters[NUM_THREADS]; // Adjacent in memory!
void work(int tid) {
for (int i = 0; i < 1000000; i++)
counters[tid]++; // Each thread increments its own counter
}`counters[0]` through `counters[15]` all live in a single 64-byte cache line. Despite each thread modifying only its own counter, every increment invalidates the line for all other cores. Performance: 10-50x slower than the single-threaded case.
Detection
- Performance Counters: High L1/L2 coherence miss rate (HITM events on Intel) despite non-shared data. Linux `perf c2c` specifically detects false sharing.
- Profiling Tools: Intel VTune's memory access analysis highlights cache lines experiencing excessive coherence traffic.
Solutions
1. Padding: Insert unused bytes between variables to place them on separate cache lines:
struct PaddedCounter {
int value;
char padding[60]; // Ensure each counter occupies its own 64-byte line
};
PaddedCounter counters[NUM_THREADS];2. Alignment: Use compiler attributes (`alignas(64)` in C++11, `__attribute__((aligned(64)))` in GCC) to force cache line alignment.
3. Thread-Local Accumulation: Each thread accumulates into a local variable (register), writing to the shared array only once at the end.
4. Data Structure Redesign: Replace arrays of per-thread values with per-thread structures spaced at cache line boundaries. Many parallel libraries (Intel TBB, Java `@Contended`) provide padded per-thread containers.
False Sharing is the performance trap that punishes the illusion of independence — threads that believe they are working on private data are secretly fighting over cache lines, and the hardware coherence protocol silently converts what should be embarrassingly parallel code into a serialized disaster.