Logically, the directory holds one entry for every block of memory. Traditional systems took that literally: with the directory controller integrated into the memory controller, they augmented memory itself — the SGI Origin added extra DRAM chips to store complete directory state alongside every memory block.
For a modern multicore with a large LLC, that design makes little sense. Off-chip directory accesses cost latency and power — even for data that’s cached on chip — and almost all memory blocks aren’t cached at any moment, so the vast DRAM directory is mostly dead weight. The fix is the classic one: cache the common case. A directory cache directory cache An on-chip cache of directory entries; DRAM-backed in traditional designs, inclusive (no backing store) in modern ones. defined in Chapter 8 — open in glossary keeps a subset of directory entries on chip, exploits the same locality that data caches do (while storing only coherence state, not data — so small ones hit often), and changes nothing about protocol functionality — only average latency. It matters more on multicores: when core-to-core messages take a handful of cycles, an off-chip directory access stops being amortized and becomes the bottleneck.
The key design question: what happens on a directory cache miss?
| DRAM-backed (§8.6.1) | Inclusive, embedded in inclusive LLC (§8.6.2) | Standalone inclusive (§8.6.2) | Null (§8.6.3) | |||
|---|---|---|---|---|---|---|
| no Recalls | with Recalls | no Recalls | with Recalls | |||
| Directory location | DRAM | LLC | LLC (own structure) | None | ||
| Uses DRAM | Yes | No | No | No | ||
| Miss implies | Must access DRAM | Block must be I | Block must be I | Any state → must broadcast | ||
| Inclusion requirement | None | LLC includes L1s | Directory cache includes L1s | None | ||
| Implementation cost | DRAM + separate on-chip cache | Larger LLC blocks; highly associative LLC | Larger LLC blocks | Highly associative redundant-tag storage | Redundant-tag storage | None |
| Replacement notification | None | None | Desirable | Required | Desirable | None |
Table 8.7 (recreated): comparing directory cache designs.
8.6.1 Directory cache backed by DRAM
Keep the complete directory in DRAM as tradition demands, and add a separate on-chip cache to cut average latency. Straightforward — and triply flawed:
- DRAM cost: state for the vast majority of blocks that aren’t cached anywhere.
- Decoupling: the directory cache and LLC track independently, so you can hit in the LLC yet miss in the directory cache — paying a DRAM trip for data sitting right there on chip.
- Writebacks: evicted directory entries must be written back to DRAM — more latency and power.
8.6.2 Inclusive directory caches
The better observation: only blocks cached on chip need directory entries at all. An inclusive directory cache inclusive directory cache Holds entries for a superset of all blocks cached on chip, so a miss means Invalid everywhere: embedded in an inclusive LLC (extra bits per block) or a standalone duplicate-tag structure (CxK associativity). defined in Chapter 8 — open in glossary holds entries for a superset of all on-chip blocks, making it a “perfect” directory cache: a miss proves the block is in state I in every cache — it is an answer, not a precursor to a slower lookup. No DRAM backing store exists.
Embedded in an inclusive LLC
If the LLC maintains inclusion over the L1s (every L1 block is also in the LLC), then “not in the LLC” already means “I in every L1.” So embed the directory in the LLC: add state bits to every LLC block.
(a) typical LLC block
(b) LLC block with LLC-embedded directory cache
The catch is LLC inclusion itself. For shared LLCs it generally requires Recall recall A directory-initiated eviction of a block from all caches, used when a limited-associativity directory-cache set fills; kept rare by sizing (at least 2x aggregate cache capacity) and non-silent PutS. defined in Chapter 8 — open in glossary requests to invalidate L1 copies whenever the LLC replaces a block. Worse, inclusion means storing redundant copies of everything the L1s hold — and on a multicore, aggregate L1 capacity can be a significant fraction of (or exceed!) the LLC.
Standalone inclusive directory cache
Drop the LLC-inclusion requirement: make the directory cache a standalone structure holding duplicate copies of every L1 tag — the union of the L1s’ contents is the set of on-chip blocks. More flexible; but now the storage is redundant tags, and the structure has a nasty shape:
Fig 8.10 (live): duplicate L1 tags, 2-way L1 caches
| Core 0 | Core 1 | Core 2 | Core 3 | |||||
|---|---|---|---|---|---|---|---|---|
| Set 0 | w0 | w1 | w0 | w1 | w0 | w1 | w0 | w1 |
| Set 1 | w0 | w1 | w0 | w1 | w0 | w1 | w0 | w1 |
| ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
| Set S−1 | w0 | w1 | w0 | w1 | w0 | w1 | w0 | w1 |
Every cell is a duplicate tag: the tag held at that core's L1 set/way. A directory-cache set spans a whole row, so it must be C×K = 4×2 = 8-way associative — and that grows linearly with core count.
There’s also bookkeeping complexity: the directory cache must track every L1 eviction, so caches must issue an explicit PutS — silent evictions are off the table (a thread §8.7.4 picks up). A common optimization piggy-backs the PutS onto the very GetS/GetX that displaces the block: same index bits, so naming which way was replaced suffices. This is called a “replacement hint” replacement hint A PutS piggybacked on a GetS/GetM naming the replaced way (same set index); required for duplicate-tag directory caches despite the name 'hint'. defined in Chapter 8 — open in glossary — misleadingly, since it is generally required, not optional.
Limiting the associativity — Recalls
Rather than build the worst-case C×K-way structure, cap it at A-way (A < C×K) and forbid the worst case: when a coherence request would add a block to a full directory-cache set, the directory first Recalls one of that set’s blocks — invalidating it from every cache that holds it, collecting acknowledgments — and only then processes the original request.
Recall-based designs live or die on sizing. Conway et al.’s rule of thumb: cover at least the aggregate capacity of the caches it includes — larger to push Recall rates down further. And they pair badly with silent S evictions: the directory would Recall blocks from caches that quietly dropped them long ago, for nothing.
8.6.3 Null directory cache (no backing store)
The cheapest directory cache null directory cache No directory state at all (Dir0B): every forwarded request is a broadcast, but the directory controller survives as the LLC controller and the protocol's ordering point. defined in Chapter 8 — open in glossary is none at all. Directory state only prunes the forwarding set — and as with coarse directories, incomplete pruning costs messages, never correctness. Take that to the limit: a Dir₀B protocol (§8.5.2) prunes nothing, needs no directory, and simply broadcasts every forwarded request to all caches. Simplistic — yet popular in small-to-medium systems, because the storage cost is zero.
If there’s no directory state, why keep a directory controller? Two reasons:
- It’s really the LLC/directory controller — it still owns the LLC.
- It is the ordering point: concurrent requests for the same block are serialized there, and it decides who was first. The protocol’s correctness never depended on the state — it depends on the serialization.
Check yourself
1.Why does the traditional DRAM-resident directory (à la SGI Origin) 'make little sense' for a multicore with a large LLC?
2.What makes a directory cache 'inclusive', and what does a MISS in one mean?
3.The standalone inclusive directory cache (duplicate L1 tags) has a scaling problem. What is it?
4.Why is the 'replacement hint' not truly a hint, and how is it commonly optimized?
5.The Null Directory Cache has no directory state at all. What two roles keep the directory controller alive?