Exercises — False sharing problem
6.1.10 · D4· Hardware › Parallelism & Multicore › False sharing problem
Yeh page ek self-test ladder hai. Har rung ek level aage sochne ka challenge deta hai — "kya tum ise pehchaan sakte ho?" (L1) se lekar "kya tum pressure mein fix invent kar sakte ho?" (L5) tak. Har problem ka pura worked solution ek collapsible callout mein chhupaaya gaya hai — pehle khud try karo, phir reveal karo.
Shuru karne se pehle, hum vocabulary fix karte hain taaki har symbol ka matlab clear ho:
Neeche sab kuch parent note False sharing problem par build kiya gaya hai. Yeh ek rule apni jeb mein rakho: hardware poori 64-byte line invalidate kar deta hai chahe do cores alag-alag bytes ko touch karein. Yahi poori bimari hai.

Level 1 — Recognition
Exercise L1.1 (Recognition)
Do global variables byte addresses aur par baithe hain. Cache line size . Core 0 sirf likhta hai; Core 1 sirf likhta hai. Kya yeh false-share karte hain?
Recall Solution
Har ek ka line index compute karo — yahi ek test hai jo matter karta hai. Same line index haan, yeh false-share karte hain. Bytes alag hain, lekin transfer ki unit (poori line 64) identical hai, toh har write doosre core ke cache se line bahar kar deti hai.
Exercise L1.2 (Recognition)
Same setup lekin ab aur . Kya yeh false-share karte hain?
Recall Solution
Alag line indices () koi false sharing nahi. Note karo , toh doosra variable exactly ek line baad shuru hota hai — woh sabse chhota gap jo unhe poori tarah separate kar deta hai.
Exercise L1.3 (Recognition)
Ek C array int counter[8] address (= 4096 decimal) par start hota hai. Ek int 4 bytes ka hota hai. In 8 counters mein se kitne pehli cache line mein land karte hain?
Recall Solution
Pehli line addresses se tak span karti hai (yeh woh 64 bytes hain jinki line index 64 hai). Har int 4 bytes occupy karta hai, toh kitne fit honge? ints ek line mein fit hote hain, lekin hamare paas sirf 8 hain, toh saare 8 counter[0..7] ek hi cache line share karte hain. Agar 8 alag cores apne-apne counter ko hammer karein, toh saatey 8 ek line par collide karte hain — worst case.
Level 2 — Application
Exercise L2.1 (Application)
int data[16] address (= 8192) par shuru hota hai. Cores elements 0 aur 15 ko read/write karte hain. Kya woh do false-share karte hain? Indices ke kaun se pairs kabhi ek line share nahi karte?
Recall Solution
int = 4 bytes, toh element par baitta hai. Element ka line index:
ke liye: . ke liye: . Same line — yeh false-share karte hain. Actually saare indices line 128 dete hain, toh is 16-int (64-byte) array ka har element ek hi line mein rehta hai. Koi bhi pair kabhi separate nahi hota — yeh array ek badi collision zone hai.
Exercise L2.2 (Application)
Slowdown estimate karo. Contention ke bina har increment ka cost cycles hai. Full false-sharing ping-pong ke under har increment ka cost cycles hai. Slowdown factor kya hai, aur increments dono cases mein kitne cycles lete hain?
Recall Solution
Slowdown factor: increments ke liye: False-sharing version identical program output ke liye 75 guna zyada kaam karta hai.
Exercise L2.3 (Application)
Tumhare paas ek struct ThreadData { int thread_id; int counter; int checksum; bool done; }; hai. Fields offsets 0, 4, 8, 12 par start hote hain. Ek single instance ek 64-byte-aligned address par shuru hota hai. Core A counter likhta hai, Core B checksum likhta hai, Core C done likhta hai. Kya teeno false-share karte hain?
Recall Solution
Kyunki struct sirf 13 bytes ka hai (16 tak padded) aur ek line boundary par start hota hai, har field ka line index struct ke base line index ke equal hai. counter (offset 4), checksum (offset 8), done (offset 12) saare ek hi 64-byte line ke andar aate hain. Teeno cores ek line par false-share karte hain — ek three-way ping-pong, two-way se bhi zyada nasty kyunki har write do remote copies ko invalidate karta hai.
Level 3 — Analysis
Exercise L3.1 (Analysis)
Minimum padding derive karo jo ek int value (4 bytes) ko isolate kare taaki in elements ka ek array kabhi false-share na kare. Phir memory waste ratio compute karo.
Recall Solution
Har element ko apni line mein force karne ke liye, consecutive elements ke beech stride hona chahiye. Toh padded struct size 64 ka multiple hona chahiye: Padding bytes chahiye: . Memory actually used 64 mein se 4 bytes hai, toh useful fraction hai; waste ratio hai. Dekho Memory allocators jisme allocators cache-aligned blocks return kar sakte hain.
Exercise L3.2 (Analysis)
Ek array PaddedCounter counter[N] jisme struct PaddedCounter { int value; char pad[60]; } hai, ek 64-byte-aligned base par start hota hai. Prove karo ki element aur element () kabhi ek line share nahi kar sakte.
Recall Solution
Element ka value par rehta hai. Uska line index hai
(division exact hai kyunki 64 ka multiple hai, aur ). Toh element ka line index hai. ke liye:
toh line indices alag hain — koi shared line nahi, koi false sharing nahi. Padding ne "same line" ko "line index har element ke saath exactly 1 badhta hai" mein badal diya.
Exercise L3.3 (Analysis)
Ek cache line 64 bytes ki hai. Ek programmer struct Bad { char pad[32]; int value; } likhta hai aur inका ek array banata hai, base ek line boundary par. Element 0 ka value offset 32 par hai. Element 1 byte 36 par start hota hai (kyunki sizeof(Bad) = 36 hai). Kya isse false sharing fix hoti hai? Elements 0 aur 1 ke liye value ke line indices compute karo.
Recall Solution
sizeof(Bad) = 32 + 4 = 36 bytes (assume karo extra alignment padding nahi hai). Element par shuru hota hai; uska value par hai.
- Element 0
value: offset , line index (base line ke relative). - Element 1
value: offset , line index .
Yahan elements 0 aur 1 ittefaqan alag lines mein land karte hain (0 vs 1) — lekin yeh offsets ki kismat hai, guarantee nahi. Element 1 value (offset 68) aur element 2 value (offset , line ) dono line index 1 dete hain → woh collide karte hain! Half-padding fragile hai: sahi fix L3.2 ki tarah full-line stride of 64 hai.
Level 4 — Synthesis
Exercise L4.1 (Synthesis)
Tumhe bataya gaya hai ki double (8 bytes each) ka ek hot array 4 cores mein false sharing cause kar raha hai, har core apna element arr[0..3] likhta hai. C++17 ka use karke ek fix design karo jo architectures mein portable ho (toh tum 64 hard-code nahi karo). Mechanism batao aur kyun yeh adapt karta hai.
Recall Solution
std::hardware_destructive_interference_size (standard ka naam " jo hot data separate karta hai") use karo alignas ke saath:
#include <new>
struct alignas(std::hardware_destructive_interference_size) Cell {
double value;
};
Cell arr[4]; // each Cell begins on its own lineMechanism: alignas(k) har Cell ko ek aise address par start hone ke liye force karta hai jo ka multiple ho, aur sizeof(Cell) bhi ke multiple tak round up hoti hai — toh stride ban jaati hai. choose karna distinct line indices guarantee karta hai (L3.2 proof). Kyun adapt karta hai: constant compiler dwara target architecture ke liye provide kiya jaata hai, toh 128-byte lines wale CPU par yeh automatically 128 ban jaata hai — koi hard-coded 64 nahi jo break ho.
Exercise L4.2 (Synthesis)
Do concerns combine karo. Ek concurrent histogram std::atomic<long> bin[8] use karta hai (har ek 8 bytes), saate 8 ek 64-byte line mein, 8 threads dwara update kiye jaate hain. Atomics races already prevent karte hain. Explain karo kyun performance phir bhi kharab hai aur do-part fix do.
Recall Solution
Atomic operations se Atomics guarantee karte hain ki har increment indivisible hai (koi torn read/write nahi), lekin ek atomic RMW ko phir bhi line Modified state mein chahiye, toh coherence invalidations phir bhi trigger hote hain. Saate 8 bins ek line mein hone se, har core ka atomic increment line bounce karata hai — parent note ka ping-pong, bilkul unchanged. Do-part fix:
- Rakho
std::atomic<long>correctness ke liye (safe concurrent updates). - Add karo padding/alignment taaki har bin ko apni line mile:
struct alignas(64) Bin { std::atomic<long> v; };
Bin bin[8];Ab correctness aur performance dono handle hain. Dekho Lock-free data structures jinmein structures yeh padding default assume karti hain.
Exercise L4.3 (Synthesis)
Memory-budget design. Tumhe 64 cores wali machine ke liye per-core counters store karne hain. Full 64-byte padding kitni memory karti hai? Agar counters zyatar read hote hain lekin rarely write, argue karo kab tum pad nahi kar sakte — aur NUMA architectures se connect karo.
Recall Solution
Padded cost: bytes = 4 KiB, versus bytes unpadded — ek blow-up, lekin absolute terms mein tiny. Kab pad na karo: False sharing sirf writes par bura lagta hai jinhe exclusive ownership chahiye. Agar counters almost hamesha read hote hain (Shared state kaafi saare caches mein simultaneously hold ho sakti hai — reads invalidate nahi karte), toh coherence traffic negligible hai aur padding sirf space aur cache capacity waste karti hai. NUMA connection: NUMA machine par ek aur bada cost yeh hai ki core ka counter remote memory mein rakha ho. Line boundary tak padding zaroori hai lekin kaafi nahi — tum chahte ho ki har core ki line uske local NUMA node mein allocate ho taaki reads/writes on-socket rahein. Dekho NUMA architectures aur Multicore scaling.
Level 5 — Mastery
Exercise L5.1 (Mastery)
Full quantitative model. cores mein se har ek apne counter par increments perform karta hai. Line size , int = 4 bytes, array unpadded aur line-aligned. Ek local write cycles hai; ek coherence-forced write cycles hai. Assume karo unpadded case mein har write coherence miss force karti hai (worst case). CPU cycles/second par chalta hai. (a) padded aur (b) unpadded ke liye wall-clock time compute karo, aur padding se speed-up.
Recall Solution
Saare cores mein total increments: . Cores parallel chalte hain, toh wall-clock time per-core kaam se set hoti hai (har core increments karta hai) — lekin full false sharing ke under ping-pong writes ko serialize karta hai, toh hum aggregate cycles ko bottleneck resource maan ke chalte hain.
(b) Unpadded (serialized ping-pong): total cycles = cycles.
(a) Padded (fully parallel, har core local): har core independently cycles karta hai, aur saate 8 ek saath chalte hain, toh wall-clock cycles = .
Speed-up: Padding ne 2-second ka kaam 3 milliseconds mein kar diya.
Exercise L5.2 (Mastery)
Scaling law. False sharing ke saath, cores add karna program ko slower bana sakta hai. Total time ko model karo (aggregate serialized coherence cost, ) versus ideal parallel ke against. L5.1 numbers use karte hue, false-sharing model mein ke liye tabulate karo aur Multicore scaling ke saath trend describe karo.
Recall Solution
, , ke saath: s.
- : s
- : s
- : s
- : s
Time core count ke saath linearly badhti hai — negative scaling. Ideal parallel time flat rehti hai s par, se independent. Yeh Multicore scaling failure ka sabse sharp illustration hai: false sharing extra cores ko benefit se coherence-traffic tax mein badal deta hai. Fix (padding) flat ideal curve restore kar deta hai.
Exercise L5.3 (Mastery)
Break-even reasoning. Padding bytes per element waste karta hai (jahan = element size). Maan lo ek design constraint tumhe per-core counters ke liye total memory budget bytes deta hai, int counters (). Kitne unpadded counters fit honge, versus padded? Agar tumhare paas exactly 8 cores hain, kya padding budget mein fit hoti hai?
Recall Solution
Unpadded: counters fit hote hain. Full line tak padded: counters fit hote hain. 8 cores ke liye tumhe 8 padded counters chahiye = bytes — haan, padding aasaani se fit hoti hai (budget ka ek chauthai use karta hai). Apparent waste per element yahan irrelevant hai kyunki 8, 32-counter ceiling se kaafi neeche hai.
Recall Self-quiz
Do addresses ke liye line index test hai ::: equal woh false-share karte hain. Elements isolate karne ke liye minimum stride hai ::: exactly (64 bytes), ek full line. Atomics fix karte hain ::: correctness (races), false-sharing performance nahi. Ek int ko ek line tak pad karna waste karta hai ::: us block ka. False sharing scaling ko ::: negative banata hai — time core count ke saath badhta hai.