Fundamentals 22 min read

Why False Sharing Slows Multi-Threaded Code Despite No Shared Data

This article explains how false sharing occurs when independent threads modify variables on the same CPU cache line, triggering MESI protocol overhead that degrades performance, and covers detection methods using perf and VTune plus mitigation techniques like cache-line padding and thread-local storage.

Deepin Linux
Deepin Linux
Deepin Linux
Why False Sharing Slows Multi-Threaded Code Despite No Shared Data

CPU Cache Mechanism

CPU speed far exceeds memory speed. To bridge this gap, CPUs use a three-level cache hierarchy:

L1 cache : closest to core, lowest latency (few cycles), split into instruction and data caches, typically tens of KB.

L2 cache : larger (hundreds of KB to few MB), slower than L1, acts as L1 backup.

L3 cache : largest, shared across cores, slower than L2. Misses here go to main memory with high latency.

A cache line (typically 64 bytes) is the smallest unit of data transfer between memory and cache. When CPU accesses arr[0], it loads the surrounding 64-byte block, so subsequent accesses to arr[1], arr[2] hit in cache — exploiting spatial locality.

MESI Cache Coherence Protocol

In multi-core systems, each core has private caches. MESI ensures consistency by tracking each cache line in one of four states:

M (Modified) : line modified in this core's cache, dirty vs memory. Must write back on eviction or other-core read.

E (Exclusive) : line only in this core's cache, clean vs memory.

S (Shared) : line present in multiple cores' caches, all clean.

I (Invalid) : line invalid, cannot be used.

Example walkthrough (C++ std::atomic<int> x{1}):

Core A reads x → line state E (exclusive).

Core B reads x → Core A snoops bus, transitions to S, supplies data; Core B loads line as S.

Core A writes x (1→2): line is S, so Core A broadcasts Invalidate; Core B marks line I, acknowledges; Core A updates to 2, state becomes M.

Core B reads x again: line is I, issues bus read; Core A (M) writes back to memory, transitions to S, supplies data; Core B loads line as S.

False Sharing: Definition and Trigger Conditions

False sharing occurs when logically independent variables used by different threads reside on the same cache line. A write by one thread forces the line into M state and invalidates other cores' copies, causing repeated cache misses and memory traffic.

Three necessary conditions:

Threads on different physical cores sharing L2/L3 (same-core threads share L1/L2, no coherence traffic).

Variables within one cache line (≤64 bytes) . Example: struct Data { long value1; long value2; }; — adjacent fields likely share a line. Arrays also: 64-byte line holds 16 int s; adjacent elements updated by different threads cause false sharing.

At least one thread writes frequently . Read-only sharing stays in S state; infrequent writes have negligible impact. High-frequency writes cause constant invalidation/reload cycles.

Step-by-Step False Sharing Scenario

Dual-core, shared L3. struct Data { long value1; long value2; } data; — both longs in same 64-byte line. Thread A (core 1) writes value1; Thread B (core 2) writes value2.

Initial : Thread A reads value1 → line loaded to core 1 L1 as E.

Other core reads : Thread B reads value2 → line loaded to core 2 L1; core 1 snoops, both become S.

Thread A writes value1 : line is S → core 1 broadcasts Invalidate; core 2 marks line I, acknowledges; core 1 updates value1, line becomes M.

Thread B reads value2 : core 2 sees I → issues bus read; core 1 (M) writes back to memory, transitions to S, supplies data; core 2 loads line as S.

Frequent writes : each write by A invalidates B's copy; each read by B triggers reload. Massive coherence traffic kills performance.

Detecting False Sharing

Hard to spot in code; use hardware performance counters.

Linux perf : perf stat -e cache-misses,cache-references your_program. Key metrics: L1-dcache-load-misses (or L1D.REPLACEMENT), MEM_LOAD_RETIRED.L1_MISS. Sudden spikes with throughput drop indicate false sharing.

Intel VTune : GUI shows per-thread cache access patterns, miss locations/frequencies. A hot cache line with many invalidations across threads signals false sharing.

Typical symptom : adding threads reduces throughput; hotspots are simple variable reads/writes (e.g., per-thread counters that don't scale).

Manual verification : pad variables to separate cache lines; if performance recovers, false sharing confirmed.

Avoiding False Sharing

Method 1: alignas (C11+)

#include <stdalign.h>

struct Counter {
    alignas(64) long count;
};
struct Counter c1, c2;
alignas(64)

forces each instance to start on a 64-byte boundary, giving count its own cache line.

Method 2: Manual Padding

struct Data {
    long value1;
    char pad[64 - sizeof(long)]; // 56 bytes on 64-bit
    long value2;
};
long

is 8 bytes; pad 56 bytes so value1 occupies a full 64-byte line, value2 lands on next line. Note : compiler/alignment rules vary; verify with offsetof.

Method 3: Thread-Local Storage

__thread long local_cnt = 0;
long total = 0;

void* worker(void* arg) {
    for (int i = 0; i < 10000000; i++) {
        local_cnt++;
    }
    total += local_cnt; // merge once
    return NULL;
}

Each thread increments its private local_cnt (no sharing), then merges once. Eliminates cross-core traffic entirely.

False sharing is a subtle performance pitfall in high-performance multi-threading: logically independent data shares a cache line, MESI coherence generates massive invalidation/reload overhead, slowing parallel code.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performance optimizationmemory alignmentCPU cachefalse sharingmulti-threadingMESI protocolthread-local storagecache line
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.