Exercises — Cache coherence at scale (directory-based)
Every term used below is built in the parent. Prerequisites you may want open: Cache coherence protocols (MESI, MOESI) · NUMA architectures · Interconnect topologies · Memory consistency models · Cache line false sharing.
Recall One-paragraph refreshers for the linked prerequisites (open if a name is new)
MESI/MOESI ::: Per-cache-line state machines. Modified = dirty, sole owner, memory stale. Exclusive = clean, sole owner, memory valid — the holder may write silently. Shared = clean, possibly many read-only copies. Invalid = not present. MOESI adds Owner: a dirty block shared for reading while one cache stays responsible for write-back. NUMA ::: Non-Uniform Memory Access: memory is split across sockets/nodes; a core reaches its local node fast and remote nodes slower. Addresses are interleaved so consecutive blocks map to different nodes (the "home" of a block). Interconnect topologies ::: The wiring between cores/directories — ring, mesh, crossbar. Determines how many hops a message takes; directory latency = number of hops to home + hops to owner. Memory consistency models ::: Rules for the order in which one core's memory operations become visible to others across different addresses. Distinct from coherence, which is per-address. Cache line false sharing ::: When two independent variables share one cache line, coherence treats them as one block — writes ping-pong ownership even though no real data is shared.
L1 — Recognition
(Can you read the machinery and name its parts?)
Exercise 1.1 — Read a directory entry
A directory entry is written as . Given the entry answer: (a) How many caches currently hold this block? (b) Is main memory's copy valid? (c) May any of these caches write the block without contacting the home directory (HD)?
Recall Solution
(a) The sharers set lists three caches (cores 1, 4, 7). (b) In Shared state all copies are read-only, so by the invariant memory is valid (it holds the definitive value). (c) No. Writing requires transitioning to Modified, which forces invalidation of the other sharers — that must go through the home directory (HD). A silent write would leave stale copies in cores 4 and 7.
Exercise 1.2 — Classify the states
Match each event to the resulting directory State (Uncached / Shared / Modified): (a) No cache anywhere holds the block. (b) Core 5 holds a dirty, exclusive copy; memory is stale. (c) Cores 2 and 8 hold identical read-only copies.
Recall Solution
(a) Uncached — nobody caches it, memory is the sole valid copy. Entry . (b) Modified — the sharers set is a singleton , so core 5 is the owner; memory stale (core 5's copy is definitive). Entry . (c) Shared — multiple read-only copies, memory valid. Entry .
Exercise 1.3 — Metadata vs. data
True or false, with one sentence why: "The directory stores a copy of each block's data so it can answer read misses without touching memory."
Recall Solution
False. The directory holds only metadata — state + the sharers set. On a read miss in Uncached/Shared state (memory valid), the data comes from main memory; in Modified state (memory stale) it comes from the owning cache (the singleton sharer). The directory never duplicates the payload.
The message-flow figures below show exactly which packets cross the wire; refer to them as you check each trace.

L2 — Application
(Can you run the protocol by hand?)
Exercise 2.1 — Read miss on Uncached
Block at 0xB000 is Uncached, memory holds . Core 6 issues a read. List the messages in order and give the final directory entry.
Recall Solution
- Core 6 → HD (home directory):
Read-Req(0xB000)— message 1. - HD sees Uncached (memory valid) → fetches from memory, replies
Data(Y=7)to core 6 — message 2. - HD updates: .
- Core 6 installs as Shared. Final entry: . Message count = 2 (Read-Req + Data reply). No acks exist on a read, so end-to-end this is genuinely 2.
Exercise 2.2 — Read miss on Modified (owner forward)
Block at 0xC000 is Modified, sharers (so core 3 is the owner), dirty value (memory still says ). Core 9 issues a read. List messages and the final entry.
Recall Solution
- Core 9 → HD:
Read-Req(0xC000)— message 1. - HD sees Modified (memory stale), sole sharer = core 3 → forwards request to core 3 — message 2.
- Core 3 → Core 9:
Data-Reply(Z=88)— message 3. - Core 3 → HD:
Data-WriteBack(Z=88)(memory now updated to 88, becomes valid) — message 4. - HD updates: . Final entry: ; memory . The old owner stays a sharer — it did not invalidate, it downgraded to read-only. Message count = 4 (Read-Req + Forward + Data-Reply + Write-Back — every crossing counted, per our ground rule).
Exercise 2.3 — Write miss on an Uncached block (Uncached → Modified)
Block at 0xE000 is Uncached, memory . Core 2 issues a write . List the messages and the final entry. How does this differ from the Shared-case write?
Recall Solution
- Core 2 → HD:
Write-Req(0xE000)— message 1. - HD sees Uncached → no sharers exist, so there is nothing to invalidate and no acks to wait for. HD replies
Write-Ack(V=5)(supplying the current value from memory so core 2 can merge/overwrite) — message 2. - HD updates: (core 2 is now the sole owner; memory becomes stale once core 2 writes).
- Core 2 stores , marks the line Modified; memory stays stale at 5 until write-back. Final entry: . Message count = 2 (Write-Req + Write-Ack). Difference from the Shared write: the Uncached→Modified path skips the whole invalidate-and-count-acks phase because the sharers set is empty. It is the cheapest write path — grant ownership immediately.
Exercise 2.4 — Write miss on Shared (invalidation)
Block at 0xD000 is Shared, sharers . Core 5 (already a sharer!) wants to write. List messages and count the invalidation acks the HD must collect.
Recall Solution
Core 5 is a sharer but only holds a read-only copy, so it still needs an upgrade (Shared → Modified).
- Core 5 → HD:
Write-Req(0xD000)(an upgrade request). - HD sends
Invalidateto the other sharers: cores 0, 2, 8 (not to core 5 — it becomes the new owner). - Cores 0, 2, 8 each → HD:
Inv-Ack. HD collects 3 acks. - After all 3 acks, HD → Core 5:
Write-Ack, then updates . Final entry: . Acks collected = 3. (Full message count end-to-end: 1 Write-Req + 3 Invalidate + 3 Inv-Ack + 1 Write-Ack = 8.)
The invalidation-and-ack handshake is shown below — trace the red invalidates and white acks.

Exercise 2.5 — The Exclusive state (MESI E)
In MESI there is a fourth state, Exclusive (E): exactly one cache holds a clean copy (memory is valid, unlike Modified). Suppose the directory grants core 4 the block on a read to an otherwise-Uncached block. (a) Why grant E instead of Shared on a lone read? (b) What can core 4 now do that a Shared holder cannot? (c) If core 7 then issues a read, what state results and does memory need updating?
Recall Solution
(a) When a read miss finds the block Uncached (no other sharers), the directory knows core 4 is the only holder. Granting Exclusive lets core 4 later write silently (E→M with no directory traffic), avoiding a future upgrade round-trip. This is a pure latency optimization.
(b) A Shared holder must send a Write-Req and wait for Write-Ack before writing. An Exclusive holder may transition E→M locally — no message needed, because the directory already knows no one else has a copy.
(c) Core 7's read reaches the HD; the HD sees the block is Exclusive at core 4. Since E is clean (memory valid), core 4 simply downgrades E→Shared and the data can come from memory (or from core 4). Result: , and memory does NOT need updating — E was never dirty. (Contrast Modified, which forces a write-back because memory was stale.)
L3 — Analysis
(Can you reason about cost and trade-offs?)
Exercise 3.1 — Full-map storage (and why the State field is 2 bits)
The full-map entry width is bits: bits for the sharer vector and 2 bits for State. First justify the "2": how many distinct states must the field encode, and why does 2 bits suffice (and would it still suffice with Exclusive added)? Then, for cores and (about 33.5 million) tracked blocks, compute the directory size in MB (take bits).
Recall Solution
Why 2 State bits. A field of bits can name distinct values. Our base protocol has 3 states (Uncached, Shared, Modified). We need with : gives only (too few), gives (enough, one code spare). So 2 bits suffice. Adding Exclusive makes 4 states, and exactly — still 2 bits, using the previously-spare code. A 5th state (e.g. MOESI's Owner) would need ⇒ 3 bits. That is why the parent's formula bakes in 2. Storage. Full-map entry width bits. bits. In MB: MB. Answer: 520 MB. Notice it scales linearly in both and — this is why full-map dies at high core counts.
Exercise 3.2 — Broadcast vs. point-to-point traffic
Under snooping, one miss broadcasts to all other cores. Under a directory, a miss touches the home + at most sharers. Using our ground-rule message definition, for and : (a) cache-endpoints reached by a snoop broadcast? (b) directory fan-out to home + sharers (requests only, not acks)? (c) the ratio of those two fan-outs. (d) Which per-miss cost is and which is ?
Recall Solution
To compare like with like we count the request-side fan-out (how many endpoints the request reaches) for both schemes; this is stated explicitly so it is not confused with the end-to-end counts of L2. (a) Snooping: 1 broadcast reaching caches → 63 endpoints. (b) Directory: 1 request to home + up to sharer messages → endpoints. (c) Ratio fewer. (d) Per miss, snooping is (broadcast touches all cores) while the directory is (bounded by , independent of ). The famous is aggregate: cores each generating -fan-out misses ⇒ system-wide — that is a whole-system rate, not the per-miss cost.
Exercise 3.3 — NUMA home lookup
A 4-socket server interleaves memory in 256B chunks. Address layout: [ tag | socket-select (2 bits) | 256B offset (8 bits) ], using our LSB-0 bit numbering. Which socket is the home for address 0x8300?
Recall Solution
Under LSB-0 numbering, bit 0 is the rightmost bit. The 256B offset occupies bits 0–7 (since ); the 2 socket-select bits sit just above, at bits 8–9.
.
Low 8 bits (offset) .
Bits 8–9: shift right by 8 → 0x8300 >> 8 = 0x83 = 1000\ 0011; its low 2 bits .
Home = Socket 3. (Interleaving above the offset spreads consecutive blocks across sockets while keeping each block's bytes local.)

L4 — Synthesis
(Can you combine states, ordering, and consistency?)
Exercise 4.1 — Full read-then-write trace with ack ordering
Block at 0xA000, initially Uncached, memory . Sequence: (1) Core 0 reads, (2) Core 3 reads, (3) Core 1 writes . Give the directory entry after each step and state exactly when Core 1 is allowed to perform its store.
Recall Solution
Step 1 — Core 0 read: Uncached → HD replies , entry becomes (memory valid). Step 2 — Core 3 read: Shared → HD adds core 3, replies , entry . Step 3 — Core 1 write:
- Core 1 → HD
Write-Req. HD sendsInvalidateto sharers (2 invalidations). - Core 0 → HD
Inv-Ack; Core 3 → HDInv-Ack. HD collects 2 acks. - Only after both acks does HD send
Write-Ackto Core 1 and set . - Core 1 now stores ; memory becomes stale until later write-back.
When may Core 1 store? After it receives
Write-Ack, i.e. after the HD confirmed both stale readers are gone. Storing earlier would let core 0 or 3 read the old 42 concurrently with the new 99 — a coherence violation.
Exercise 4.2 — Consistency ordering
Two cores each run code. Core A: store flag = 1. Core B: spins while(flag == 0); then load data. Explain, in directory terms, why the ack-before-write-ack rule is what makes B never keep reading a stale flag. Then state precisely what coherence alone does not guarantee here, and which mechanism supplies it. Relate to Memory consistency models.
Recall Solution
Part 1 — why B stops seeing the old flag. Assume flag starts Shared (both cores cache it read-only, value 0). When Core A executes store flag = 1:
- A cannot write from Shared; it sends
Write-Reqto the HD. - The HD sends
Invalidateto B's copy offlagand waits for B'sInv-Ackbefore granting A the write (Write-Ack), exactly the rule of Ex 4.1. - B's cached
flagis now Invalid, so B's next loop iteration misses and must re-fetch — it physically cannot keep reading the stale 0. Once A's write completes, that re-fetch returns 1 and the spin exits. This handshake is what enforces the per-block coherence invariant: a single agreed value offlagvisible to all. Part 2 — what coherence does NOT give you. Coherence orders operations to one address (flag). It says nothing about whether A's earlierstore databecomes visible to B before A'sstore flag = 1. If the hardware reorders A's two stores, B could seeflag == 1yet load a staledata. Fixing cross-address ordering is the job of the memory consistency model (e.g. a release fence on A's store, an acquire on B's load). See Memory consistency models. Takeaway: coherence (this note) + a consistency model together make the flag/data handshake correct; neither alone suffices.
L5 — Mastery
(Can you design and justify?)
Exercise 5.1 — Choose the protocol
You are architecting two chips: (A) an 8-core desktop CPU with a shared ring bus; (B) a 96-core, 6-chiplet server with no shared bus. For each, pick snooping or directory and justify with the scaling argument.
Recall Solution
Chip A (8 cores, shared bus): choose snooping. Broadcast latency is a single low-latency hop; per-miss traffic is only endpoints — cheap. Directory indirection would add a hop with no bandwidth payoff. (Matches Intel Core i7.) Chip B (96 cores, 6 chiplets, no bus): choose directory. There is physically no shared bus to snoop, and broadcast to 95 caches would saturate the interconnect. Point-to-point (home + few sharers) keeps per-miss traffic . Distribute the directory across chiplets so no single slice is a hot-spot. (Matches AMD EPYC.) Principle: protocol follows scale and topology, not "newness".
Exercise 5.2 — Design a limited-pointer directory
For cores you want each directory entry to hold at most sharer IDs (not a full bit-vector) plus 2 state bits. (a) How many bits does one entry cost? (b) Compare against full-map per-entry width. (c) Name the correctness hazard your design introduces.
Recall Solution
(a) Each sharer ID needs bits. Four pointers bits, plus 2 state bits bits per entry. (b) Full-map per entry bits. Limited-pointer is bits — about smaller. (c) Overflow hazard: if a 5th core wants to share, there's no slot. See Ex 5.3 for exactly what the hardware does then.
Exercise 5.3 — Sharer-set overflow: what happens beyond
Continue Ex 5.2's design ( pointers). Block currently has sharers — the entry is full. Now core 100 issues a read of . (a) Why can't the entry just record core 100? (b) Give the two standard hardware policies for handling this overflow and their cost. (c) When core 200 later writes , how does each policy pay for the overflow?
Recall Solution
(a) The entry has exactly pointer slots and all four are occupied by . There is no encoding for a 5th distinct sharer ID — the hardware assumed a maximum of . So core 100 cannot simply be added. (b) Two standard policies:
- Broadcast fallback (coarse vector): the entry flips to a special "too many sharers" mode — it stops tracking individuals and marks the block as "shared by everyone (or by a coarse group of cores)." Cost: future invalidations must broadcast to that whole group, losing the point-to-point win for this block.
- Eviction / forced invalidation: the directory picks one current sharer (say core 1), sends it an
Invalidate, collects the ack, frees the slot, and inserts core 100. Cost: a false invalidation — core 1 loses a perfectly valid read-only copy and will miss if it reads again. (c) When core 200 writes (Shared → Modified): - Under broadcast fallback, the HD must send
Invalidateto the entire coarse group and collect all their acks — potentially dozens of messages, even though only 4–5 cores truly cached . - Under eviction, the sharer set never exceeded 4, so the write invalidates only those ≤4 tracked cores — cheap invalidation, but some earlier reader (core 1) already paid with a false invalidation. Design lesson: limited pointers trade storage (34 vs 258 bits, Ex 5.2) for occasional traffic/false invalidations at overflow. Pick to cover the common sharing degree so overflow is rare.
Exercise 5.4 — False sharing meets the directory
Cores 4 and 7 write to different variables that happen to sit in the same 64B cache block. Trace what the directory does on each alternating write, and connect it to Cache line false sharing. Why is this expensive even though the two cores never touch the same bytes?
Recall Solution
The directory tracks coherence at block granularity, not variable granularity. So:
- Core 4 writes its var → block goes Modified, sharers ; core 7's copy is invalidated.
- Core 7 writes its var → HD invalidates core 4, ownership ping-pongs to sharers .
- Repeat → the block bounces between the two owners on every write. This is false sharing: the variables are independent, but the block is shared, so the coherence protocol serializes them. Cost = repeated invalidations + owner-forwarding hops, even though no true data dependence exists. Fix (design mastery): pad the two variables into separate cache lines so their directory entries are independent. See Cache line false sharing.
Recall Self-test checklist (reveal after finishing)
How many caches hold ? ::: Three. In Modified state, where is the "owner" stored? ::: It is the single element of the Sharers set — no separate owner field. What does "memory stale" mean and which state has it? ::: A cache holds a newer value than memory, so memory is not the definitive copy — true in Modified. On a read miss to a Modified block, who supplies the data? ::: The owning cache (via forward + write-back), not the directory. End-to-end messages for a read miss on an Uncached block? ::: 2 (Read-Req + Data reply; no acks on reads). What can an Exclusive holder do that a Shared holder cannot? ::: Write silently (E→M) with no directory messages. Invalidations on a write from sharer 5 with sharers ? ::: 3 (all sharers except the writer). Why does the State field need only 2 bits (base protocol)? ::: 2 bits encode codes, enough for 3 (or 4 with Exclusive) states. Full-map directory size for , blocks? ::: 520 MB. Per-miss request fan-out: snooping vs directory (big-O)? ::: vs ; the is aggregate, not per-miss. What happens when a limited (-pointer) directory's sharer set overflows? ::: Broadcast fallback (coarse vector) or evict a sharer via forced/false invalidation. What per-line trap makes disjoint variables expensive? ::: False sharing.