Home
Dec 2, 2025

False Sharing Explained Simply

cpucachehardwareperformance

CPUs don’t read memory one byte at a time. When a core needs a variable, it pulls in the whole cache line that contains it, on most modern CPUs that’s 64 bytes.

This is usually great: if you read array[0], then array[1] is probably already in cache. But it has a sneaky downside when multiple cores are involved.

The Problem

Say two threads run on two different cores, and each one increments its own counter:

1type Counters struct {
2    a int64 // updated by core 1
3    b int64 // updated by core 2
4}

a and b are different variables, no lock needed, no data race. Each core should be able to hammer its own counter at full speed, right?

Except a and b are 8 bytes each, sitting right next to each other, so they almost certainly live on the same 64-byte cache line.

Cache coherence works at the granularity of a cache line, not a variable. When core 1 writes to a, the coherence protocol invalidates that line in core 2’s cache. When core 2 then writes to b, it has to fetch the line back, which invalidates it in core 1’s cache. And so on, forever.

The two cores are fighting over a line they don’t actually share any data on. That’s why it’s called false sharing, from the program’s point of view nothing is shared, but from the hardware’s point of view everything is.

How Bad Is It?

Bad. The cache line keeps bouncing between the cores’ caches, and every bounce costs roughly as much as an L2/L3 access instead of an L1 hit. I’ve seen benchmarks where fixing false sharing made a counter loop 5–10x faster with zero logic changes.

The Fix

Padding. You make sure each hot variable gets its own cache line:

1type Counters struct {
2    a int64
3    _ [56]byte // pad to 64 bytes
4    b int64
5}

It feels wasteful to burn 56 bytes on nothing, but memory is cheap and cache line ping-pong is not.

The Go runtime does this internally, if you grep the source for cpu.CacheLinePad you’ll find padding in the scheduler and in sync.Pool, exactly for this reason.

The general lesson: on multicore machines, how you lay out your data matters as much as what your code does with it.