False sharing costs more than you think
Cache coherence operates on cache lines, not on variables. On x86-64 a line is 64 bytes. When two cores write to different variables that happen to land on the same line, the line ping-pongs between their caches through the coherence protocol — each write invalidating the other core's copy — even though the two variables are completely independent.
The classic shape is an array of per-thread counters:
struct Counters {
hits: [u64; 8], // eight threads, eight counters
} // ... and all eight live in one cache line
Eight u64s are exactly 64 bytes. Every increment from every thread contends for the
same line. The code is lock-free and looks perfect; it performs worse than a single mutex-protected
counter, because a mutex at least keeps the contention explicit and lets threads back off.
The fix
Pad each element out to its own line:
#[repr(align(64))]
struct Padded(u64);
Note that crossbeam's CachePadded uses 128 bytes on x86-64, not 64.
That is deliberate: Intel's adjacent-line prefetcher pulls lines in pairs, so two variables 64 bytes
apart can still interfere. If you are hand-rolling the padding and measuring on Intel, try 128
before concluding that false sharing was not the problem.
Finding it
perf c2c exists for exactly this and reports the offending cache lines and the
instructions touching them. It is far more direct than staring at a flame graph, where false sharing
shows up as an innocuous-looking hot instruction with no obvious reason to be hot.