CPU architecture
CPU Caches and False Sharing

What are CPU caches?
Physical RAM is much slower than a modern CPU core. If every load and store had to wait on main memory, the processor would spend a large amount of time idle. CPU caches exist to bridge that speed gap by keeping recently used instructions and data close to the execution units.
Modern CPUs usually organize cache into multiple levels. L1 is the smallest and fastest, typically private to a core. L2 is larger and still commonly private to a core or a small group of cores. L3, also called the last-level cache, is larger again and is commonly shared across cores.
- L1 cache: fast private cache close to the core, often split between instructions and data.
- L2 cache: a larger private staging area for data that does not fit in L1.
- L3 cache: a shared last-level cache that helps cores exchange data without immediately falling back to RAM.

Multi-core threads
When a process has multiple runnable threads and multiple cores are available, the scheduler can run those threads at the same time on separate cores. That is great for parallelism, but it also means the cores must keep their private caches coherent.
pthread_create(&thread_a, NULL, worker_a, NULL);
pthread_create(&thread_b, NULL, worker_b, NULL);In a typical layout, each core has its own L1 and L2 cache, while a larger L3 cache is shared. Data can move among RAM, L3, and the private per-core caches depending on what each thread touches.

Understanding cache lines
A CPU does not usually fetch only the exact byte requested by a program. It fetches a fixed-size block of neighboring bytes called a cache line. On common x86 and ARM systems, 64 bytes is the standard cache line size.
This is normally a performance win. Nearby data often gets used soon, so fetching a 64-byte line improves locality and reduces trips to slower memory. The same optimization becomes dangerous when two different cores repeatedly modify different variables that happen to live inside that same 64-byte line.
False sharing
False sharing occurs when independent fields are placed close enough together that they occupy the same cache line. The variables are logically separate in the program, but the hardware cache coherence protocol sees them as part of one shared unit.
typedef struct {
volatile long a;
volatile long b;
} BadStruct;In this structure, a and b can land in the same 64-byte cache line. If one core modifies a while another modifies b, the cores keep invalidating each other's cached line even though neither thread needs the other variable.

void *worker_a(void *arg) {
for (long i = 0; i < 500000000; i++) {
shared_data.a++;
}
return NULL;
}
void *worker_b(void *arg) {
for (long i = 0; i < 500000000; i++) {
shared_data.b++;
}
return NULL;
}Reads alone are not the painful part. The pain starts when both threads write. When Core 1 updates a, Core 2's cached copy of the whole line becomes invalid. When Core 2 updates b, Core 1's copy is invalidated. The line bounces between cores, and useful parallel work turns into coherence traffic.

Experiment: false sharing
The experiment measures L1 data-cache loads and misses while two threads increment adjacent fields in the bad structure.
perf stat -e L1-dcache-loads,L1-dcache-load-misses ./bad
Performance counter stats for './bad':
2,001,927,676 L1-dcache-loads
59,213,703 L1-dcache-load-misses # 2.96% of all L1-dcache accesses
2.447781058 seconds time elapsedThe result shows a visible L1 miss rate while the two cores fight over a cache line that contains both variables.
Fix: separate the cache lines
The fix is to make the data layout match the hardware reality. By adding padding between the fields, a and b can sit on separate cache lines.
typedef struct {
volatile long a;
char pad[64 - sizeof(long)];
volatile long b;
} GoodStruct;Because a long is 8 bytes on the tested platform, the padding is 56 bytes. That makes the next field start in a different 64-byte line, allowing each thread to update its own line without invalidating the other core's working data.


perf stat -e L1-dcache-loads,L1-dcache-load-misses ./good
Performance counter stats for './good':
2,003,243,991 L1-dcache-loads
56,077 L1-dcache-load-misses # 0.00% of all L1-dcache accesses
2.714035093 seconds time elapsedSummary
- The bottleneck: RAM access is expensive, so CPUs rely on nearby caches to reduce latency.
- The transfer unit: cache traffic happens in fixed-size cache lines, commonly 64 bytes.
- The conflict: two cores modifying different variables in the same line can invalidate each other's cache copies.
- The fix: align or pad hot per-thread fields so unrelated writers do not share the same cache line.