# False Sharing: The Cache Line That Kills Your Parallelism

> You split work across eight threads with zero shared state, and it runs slower than one thread. The culprit is false sharing — independent variables that happen to live on the same cache line, fighting over it.

URL: https://harshith.in/blog/false-sharing/
Author: Harshith Sunku (Staff Engineer) — https://harshith.in/about/
Tags: performance, concurrency

You parallelize a counter-per-thread loop. No locks, no shared variables — each
thread writes its own slot. It should scale linearly. Instead, eight threads are
slower than one. Welcome to false sharing: the performance bug where memory that
*isn't* shared behaves as if it is.

## Cores Don't Trade Bytes, They Trade Cache Lines

A CPU never moves a single byte between cores. The unit of coherence is the
**cache line** — 64 bytes on most x86. When a core writes any byte in a line, the
cache-coherence protocol (MESI) must invalidate that line in every other core's
cache:

```text
   one 64-byte cache line
   ┌───────────────────────────────────────┐
   │ counter[0] counter[1] ... counter[7]  │   ← all on ONE line
   └───────────────────────────────────────┘
   thread 0 writes counter[0]  → invalidates the line everywhere
   thread 1 writes counter[1]  → invalidates it again
```

The variables are independent, but they ride the same line. Every write by any
thread evicts the line from every other core, forcing a re-fetch across the cache
hierarchy. The line ping-pongs between cores at memory speed.

## Seeing It

The classic reproduction — an array of per-thread counters:

```c
struct { long count; } counters[8];   // 8 longs = 64 bytes = ONE line

// thread i hammers counters[i].count
for (long j = 0; j < 100000000; j++)
    counters[i].count++;
```

This will scale *negatively*. The fix is to push each counter onto its own line:

```c
struct {
    long count;
    char pad[64 - sizeof(long)];   // pad out to a full cache line
} counters[8];
```

Now each thread owns a private line — no invalidation traffic, and the loop
scales the way you expected.

```text
   before:  [c0 c1 c2 c3 c4 c5 c6 c7]          one contested line
   after:   [c0 ......][c1 ......][c2 ...] ... one line each
```

## Doing It Cleanly

Hand-padding is error-prone. C++ gives you the line size and an alignment tool:

```cpp
#include <new>
struct alignas(std::hardware_destructive_interference_size) Counter {
    long count;
};
```

In C, `alignas(64)` plus padding does the same job.

## How to Catch It

You can't see false sharing in the code — only in the hardware counters. `perf`
exposes the coherence traffic:

```bash
perf c2c record ./your_program     # cache-to-cache transfer analysis
perf c2c report                    # points at the contended cache lines
```

A workload that should scale but flatlines, with high cache-coherence or
HITM events, is the signature.

## Takeaways

- Cores exchange whole 64-byte cache lines, not individual variables.
- False sharing happens when independent data lands on one line, so each write
  invalidates the line in every other core — turning parallel code serial.
- Pad or align hot per-thread data to its own cache line to fix it.
- Use `perf c2c` to find it; it's invisible in source but obvious in coherence
  counters.
