Intuition Why a whole page of examples?
The parent note told you what false sharing is and why the hardware can't see it. This page does something different: it walks you through every kind of situation you could ever meet — different variable spacings, zero-cost cases, the degenerate "everything already fits" case, and the limiting case of thousands of cores. By the end, no layout should surprise you. You will be able to predict whether a piece of code ping-pongs a cache line before you even run it.
Before anything else, let's pin down the one measurement everything rests on.
Definition Cache line, byte address, line index
A byte address is just a number naming one byte of memory, like house number 4096 on a street. We write addresses in hex (base-16), so 0x1000 means the decimal number 4096 .
A cache line is the chunk the hardware copies around — always the same fixed size L bytes (almost always L = 64 ). Think of the street divided into blocks of 64 houses.
The line index of an address A is which block it falls in:
line ( A ) = ⌊ L A ⌋
The symbol ⌊ x ⌋ (the floor ) means "round down to the nearest whole number". We use floor here because block membership is all-or-nothing: address 63 and address 0 are both in block 0; address 64 jumps to block 1.
Intuition Why floor-divide and not something fancier?
We want to answer one yes/no question: "do two addresses share a line?" Two addresses share a line exactly when they land in the same block, i.e. when their line indices are equal. Dividing by L and dropping the remainder is precisely "which block?" — nothing more is needed. That single test drives every example below.
Every false-sharing situation is one of these case classes . Each worked example is tagged with the cell it fills.
#
Case class
The question it answers
Example
A
Adjacent, tightly packed
Do neighbouring array elements share a line?
Ex 1
B
Boundary straddle
What if one variable spans two lines?
Ex 2
C
Zero-cost / no sharing
When does spacing already save us?
Ex 3
D
Degenerate: single writer
Is it still false sharing with one core?
Ex 4
E
Padding fix, exact
How much padding is just enough ?
Ex 5
F
Over-alignment trap
Why 8-byte align fails, 64 works
Ex 6
G
Limiting case: many cores
How does cost scale as cores → large?
Ex 7
H
Real-world word problem
Timing a counter benchmark end-to-end
Ex 8
I
Exam twist: partial sharing
Some cores clash, some don't — count them
Ex 9
We build the same tool for all of them: compute line ( A ) = ⌊ A /64 ⌋ for each variable, then check which indices collide.
Worked example Example 1 — Case A: Adjacent packed counters
An int is 4 bytes. We have int counter[4] starting at address 0x1000. Cores 0–3 each increment their own element in a loop. Which elements share a cache line?
Forecast: Guess now — how many distinct cache lines do these four counters occupy? One? Two? Four?
Write down each address. Element i sits at 0 x 1000 + 4 i : so 0x1000, 0x1004, 0x1008, 0x100C.
Why this step? Addresses are the only thing the hardware sees. Variable names mean nothing to the coherence protocol.
Convert 0x1000 to decimal. 0 x 1000 = 4096 . The four addresses are 4096 , 4100 , 4104 , 4108 .
Why this step? The line-index formula divides a plain number; we must leave hex first.
Compute each line index with ⌊ A /64 ⌋ : ⌊ 4096/64 ⌋ = 64 , ⌊ 4100/64 ⌋ = 64 , ⌊ 4104/64 ⌋ = 64 , ⌊ 4108/64 ⌋ = 64 .
Why this step? Equal line indices ⇒ same line ⇒ false sharing. All four are index 64.
Conclusion: all four counters live in one cache line. Every core's increment invalidates the other three copies → maximal ping-pong.
Why this step? This is the classic worst case the parent note warned about.
Verify: 4096 = 64 × 64 exactly, and 4108 − 4096 = 12 < 64 , so all four fit inside block [ 4096 , 4159 ] . Answer: 1 line , full contention. ✓
Worked example Example 2 — Case B: Boundary straddle
A struct is 16 bytes, and an array of them begins at 0x1000. Which array element is the first one whose start address lands in a new cache line, and does any single 16-byte element ever get split across two lines here?
Forecast: Will any element be cut in half by a line boundary?
Element i starts at 0 x 1000 + 16 i = 4096 + 16 i . Elements: 4096, 4112, 4128, 4144, 4160, ...
Why this step? Stride is sizeof(struct) = 16, not 4; the spacing sets everything.
Line index of each: ⌊ 4096/64 ⌋ = 64 , ⌊ 4112/64 ⌋ = 64 , ⌊ 4128/64 ⌋ = 64 , ⌊ 4144/64 ⌋ = 64 , ⌊ 4160/64 ⌋ = 65 .
Why this step? We watch for the first jump in the index. It happens at element 4 (0x1010 = 4160), the start of line 65.
Check for a split element. Line 64 covers bytes [ 4096 , 4159 ] . Element 3 starts at 4144 and ends at 4144 + 15 = 4159 — fits exactly. Element 4 starts fresh at 4160.
Why this step? A split happens only if an element's start and end fall in different blocks. Since 4096 is a multiple of 64 and 16 divides 64, no 16-byte element ever straddles.
Conclusion: first element in a new line is element 4 ; no element straddles because 16 divides 64 evenly and the base is line-aligned.
Why this step? Straddling matters when a struct is not a divisor of L — then one variable can cost two invalidations.
Verify: Elements 0–3 share line 64; element 4 opens line 65. 4160 = 65 × 64 . ✓
Worked example Example 3 — Case C: Zero-cost, already isolated
Two int variables, a at 0x2000 and b at 0x2040. Two cores hammer one each. Is there false sharing?
Forecast: Same line or different lines?
Decimal addresses: 0 x 2000 = 8192 , 0 x 2040 = 8256 .
Why this step? Convert before flooring.
Line indices: ⌊ 8192/64 ⌋ = 128 , ⌊ 8256/64 ⌋ = 129 .
Why this step? 8256 − 8192 = 64 , exactly one line apart.
Different indices → no shared line → no coherence contention.
Why this step? This is the goal state : writes stay local, ~1–2 cycles each.
Verify: 8256 = 129 × 64 and 8192 = 128 × 64 ; indices differ. No false sharing. ✓
This connects to Cache line size and alignment : place hot per-core data ≥ L bytes apart.
Worked example Example 4 — Case D: Degenerate single writer
int counter[4] at 0x1000, but only Core 0 ever writes; Cores 1–3 never touch the array. Is there false sharing?
Forecast: Same layout as Example 1 — does the answer change?
Recall the definition: false sharing needs writes from different cores to the same line .
Why this step? The layout alone is not enough; there must be a conflict of writers.
Here only one core writes. The line sits Modified in Core 0's cache and never gets invalidated by anyone else.
Why this step? With a single writer, the MESI Modified state is never disturbed.
Conclusion: identical addresses to Example 1, but zero false sharing — because there is no second writer to trigger invalidation.
Why this step? This is the crucial degenerate case: false sharing is a property of the access pattern , not of the layout alone.
Verify: Number of invalidating remote writers = 0 ⇒ coherence traffic = 0 . ✓ (See Cache coherence protocols for why a lone Modified line stays cheap.)
Worked example Example 5 — Case E: Exact padding fix
We want each int counter on its own 64-byte line. What is the padded struct size, and how many padding bytes go after the 4-byte int?
Forecast: 60 bytes of padding? 64? Something else?
Padded size formula. To round sizeof = 4 up to a whole number of lines:
padded = ⌈ 64 4 ⌉ × 64 = 1 × 64 = 64 bytes .
The symbol ⌈ x ⌉ (ceiling ) rounds up — we round up because we can never use less than one whole line to isolate.
Why this step? Any leftover in the line would let a neighbour share it.
Padding bytes = padded − payload = 64 − 4 = 60 bytes.
Why this step? The int uses 4, so 60 bytes of filler complete the line.
Check separation. Element i now starts at 0 x 1000 + 64 i . Line indices: ⌊( 4096 + 64 i ) /64 ⌋ = 64 + i — all distinct.
Why this step? Distinct indices guarantee the fix worked; this is the padding formula from the parent note in action.
Verify: Padded size 64 , padding 60 , and consecutive line indices 64 , 65 , 66 , 67 are all different. ✓ (Cross-check the C++17 route in Cache line size and alignment .)
Worked example Example 6 — Case F: The over-alignment trap
A colleague aligns each counter to 8 bytes and expects the false sharing to vanish. Addresses become 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38. How many share the first cache line?
Forecast: Does 8-byte alignment fix it?
List decimal addresses: 0 , 8 , 16 , 24 , 32 , 40 , 48 , 56 .
Why this step? Alignment sets the stride , but 8 ≪ 64.
Line index of each: ⌊ 0/64 ⌋ through ⌊ 56/64 ⌋ all equal 0 .
Why this step? Every address below 64 is in line 0. Eight counters, one line.
Contrast 64-byte alignment: addresses 0 , 64 , 128 , 192 , … give line indices 0 , 1 , 2 , 3 , … — all distinct.
Why this step? The alignment must equal (or exceed) L to guarantee isolation; anything smaller repacks multiple variables per line.
Conclusion: 8-byte alignment leaves 8 counters in line 0 → still full false sharing. Only ≥ 64 -byte alignment works.
Why this step? This is the exact mistake flagged in the parent note.
Verify: ⌊ 56/64 ⌋ = 0 so all 8 in line 0; ⌊ 64/64 ⌋ = 1 = 0 so 64-align separates. ✓
Worked example Example 7 — Case G: Limiting behaviour, many cores
N cores each own one int in a single shared 64-byte line and all spin-increment. Roughly how does total coherence cost scale as N grows? Take a single remote write at ≈ 150 cycles.
Forecast: Linear in N ? Quadratic? Constant?
Per line-transfer cost is fixed at T coherence ≈ 150 cycles (one invalidate-and-fetch round trip).
Why this step? This is the atomic unit of ping-pong cost.
Rounds until every core has written once: in the worst case the line bounces between cores, so producing K total writes across all cores costs about K × 150 cycles — the writes serialize .
Why this step? Only one core can hold the line Modified at a time, so parallelism collapses to a queue.
Limiting behaviour: for K writes total, time ≈ 150 K cycles regardless of how you split them among cores — adding cores gives no speedup , and the effective throughput falls as N rises because contention worsens.
Why this step? This is why false sharing destroys Multicore scaling : the workload that "should" be embarrassingly parallel runs strictly serially.
Numeric anchor: K = 1 0 6 contended writes ⇒≈ 150 × 1 0 6 = 1.5 × 1 0 8 cycles. On a 3 GHz core that is 0.05 s of pure coherence stall.
Why this step? Turns the abstract scaling into a wall-clock feel.
Verify: 150 × 1 0 6 = 1.5 × 1 0 8 cycles; 3 × 1 0 9 1.5 × 1 0 8 = 0.05 s. ✓
Worked example Example 8 — Case H: Real-world benchmark
Two configs run 1 0 7 increments per core on 4 cores. Padded version: each write ≈ 2 cycles. False-shared version: each write ≈ 100 cycles. Core clock = 2 GHz . Find both wall-clock times and the slowdown factor.
Forecast: How many seconds apart are they?
Padded total cycles = writes × cost. But padded writes proceed in parallel across 4 cores, so per-core time uses 1 0 7 writes: 1 0 7 × 2 = 2 × 1 0 7 cycles per core.
Why this step? No shared line → cores overlap → measure one core.
Padded time: 2 × 1 0 9 2 × 1 0 7 = 0.01 s.
Why this step? cycles ÷ (cycles/second) = seconds. Units: cycles / s cycles = s .
False-shared total serializes: all 4 × 1 0 7 = 4 × 1 0 7 writes queue, each 100 cycles: 4 × 1 0 7 × 100 = 4 × 1 0 9 cycles.
Why this step? Contention forces one-at-a-time (Example 7's logic).
False-shared time: 2 × 1 0 9 4 × 1 0 9 = 2 s.
Why this step? Same clock conversion.
Slowdown: 0.01 2 = 200 × .
Why this step? Quantifies the disaster.
Verify: padded 0.01 s, shared 2 s, ratio 200 . Sanity: matches order-of-magnitude "50–100×+" from the parent note (higher here because we also serialize across 4 cores). ✓
Worked example Example 9 — Case I: Exam twist, partial sharing
Eight int counters at 0x3000, so addresses 0x3000, 0x3004, ..., 0x301C (first 8). Cache line L = 64 . Eight cores each write one. How many distinct cache lines , and how many cores clash per line ?
Forecast: All 8 in one line, or split?
Addresses: 0 x 3000 = 12288 up to 12288 + 4 × 7 = 12316 .
Why this step? Stride 4 bytes across 8 ints spans 32 bytes.
Line indices: ⌊ 12288/64 ⌋ = 192 and ⌊ 12316/64 ⌋ = 192 — every one is 192.
Why this step? 12316 − 12288 = 28 < 64 , so all fit in line 192.
Count: 1 distinct line, 8 cores clashing on it — the densest possible contention.
Why this step? This is the trap answer: people assume 8 ints "must" spread out, but 32 bytes is half a line.
Twist follow-up: to split into two groups of 4 (2 lines), pad each int to 8 bytes? No — Example 6 killed that. You need 64-byte stride so each opens its own line: 8 counters → 8 lines.
Why this step? Reinforces that only ≥ L stride isolates.
Verify: ⌊ 12316/64 ⌋ = 192 = ⌊ 12288/64 ⌋ ; one line, 8 writers. ✓
Recall Self-test
All hex→decimal, then ⌊ A /64 ⌋ . Do two variables 40 bytes apart starting at a line boundary share a line? ::: Yes — 40 < 64 , both floor to the same index.
How many padding bytes isolate a 4-byte int on a 64-byte line? ::: 64 − 4 = 60 bytes.
Does 8-byte alignment stop false sharing? ::: No — 8 variables still pack into one 64-byte line.
Is there false sharing if only one core ever writes the line? ::: No — false sharing needs writes from ≥ 2 different cores.
Why does adding cores not help a false-shared counter loop? ::: The single Modified line serializes all writes; parallelism collapses.
Mnemonic The one test to rule them all
"Floor the address by sixty-four; equal indices, ping-pong for sure."
Related building blocks: Cache coherence protocols · Cache line size and alignment · Atomic operations · Lock-free data structures · Memory allocators · NUMA architectures · Multicore scaling · back to the parent topic .