A core count alone cannot choose a coherence design. Ask how often requests occur, how widely lines are shared, what interconnect ordering exists, where metadata fits, and where contention accumulates. Snooping and directories organize discovery and ordering; MSI, MESI and MOESI describe permissions and dirty responsibility. Choosing a directory does not require choosing MOESI.
Prerequisite: the course model's state meanings. C0 and C1 have private write-back caches; H contains the directory and memory controller; X is one location in a 64-byte line and resets to uncached value 0 in memory. The following rows are independent legal snapshots, not steps in a transaction. No requests overlap.
Snapshot | C0 state / value | C1 state / value | H directory | Memory value | Permission and newest value |
|---|---|---|---|---|---|
Reset | I / - | I / - | U | 0 | No reader or writer; memory has newest 0 |
Shared | S / 0 | S / 0 | S{C0,C1} | 0 | Two readers, no writer; memory and both caches have 0 |
Exclusive clean, E (MESI, MOESI) | E / 0 | I / - | exclusive(C0) | 0 | C0 may write; memory and C0 have 0 |
Exclusive dirty, the M state | M / 7 | I / - | exclusive(C0) | 0 (stale) | Only C0 may write; C0 has newest 7 |
Dirty shared, O (MOESI) | O / 7 | S / 7 | O: C0, sharers {C1} | 0 (stale) | Two readers, no writer; C0 and C1 have 7; C0 is dirty-responsible |
Notice what metadata cannot say: exclusive(C0) does not distinguish the E row from the M row after a silent store. In the MOESI row H names C0 as the dirty-responsible holder and C1 as a sharer; O supplies dirty responsibility without write permission. MSI and MESI cannot settle a remote dirty read as S / S while memory remains stale; the dirty value must reach memory before that shared state settles. These constraints remain the same whichever discovery scheme is selected.
Home-ordered broadcast is the middle branch: H receives every request and broadcasts it in its own order, so H rather than a bus is the point of serialization, but every cache is still asked, and each must reply, because an unordered fabric gives no snoop phase that ends on its own.
The entry from Directory, Home and Sharers is a full map: 2 state bits (U, S or exclusive) plus one sharer bit per cache, with the exclusive holder id encoded in the sharer bits. The numbers below are illustrative, per 64-byte line of data, and ignore tag and ECC bits.
LINE_BYTES = 64
DATA_BITS = LINE_BYTES * 8
STATE_BITS = 2 # U, S, exclusive: three values need 2 bits
def entry_bits(n_caches):
# one sharer bit per cache; the exclusive holder is the single set bit
return n_caches + STATE_BITS
def overhead_pct(n_caches):
return 100 * entry_bits(n_caches) / DATA_BITS
assert DATA_BITS == 512
assert entry_bits(2) == 4 and round(overhead_pct(2), 2) == 0.78
assert entry_bits(64) == 66 and round(overhead_pct(64), 1) == 12.9
print(f"PASS: full-map entry, 2 caches {entry_bits(2)} bits ({overhead_pct(2):.2f}%), "
f"64 caches {entry_bits(64)} bits ({overhead_pct(64):.1f}%) of 512 data bits")For the course model the entry is 4 bits, 0.78% of the line. At 64 caches the sharer bits dominate: 66 bits, 12.9%, and the share keeps growing linearly with N. That growth is why larger designs use limited or coarse entries, covered in Hybrids and Snoop Filters.
The rows below are structural, not measured: they count who must act, not how long it takes.
Question | Ordered-bus snooping | Directory targeting |
|---|---|---|
Who inspects a request? | Every relevant snooper | H and selected possible holders |
Where is conflicting work ordered? | Shared request order plus completion rules | H serializes X and tracks unfinished work |
Cache-to-cache miss path | 2 legs: broadcast request, holder supplies data | 3 legs with direct data (Part 3); 4 in this course's model, which routes data through H. Plus a directory lookup. |
Growth with N caches | About N-1 tag checks per miss on one shared ordering point | Messages proportional to actual sharers; metadata per line grows with N (see the entry cost above) |
What can become expensive? | Broadcast fanout, cache tag checks and shared bus use | Holder metadata, home lookup and home contention |
What workload matters? | Request rate and broadcast domain size | Sharing degree, locality and metadata accuracy |
For two cores, a broadcast checks just one peer, so targeting has little fanout to remove. This is a topology observation, not a latency benchmark. Wider systems need a measured request mix and resource budgets before a conclusion. The Primer, second edition, chapters 7 and 8 treats snooping and directory designs as implementation alternatives with different ordering and communication costs.
Broadcast work per miss grows with the number of caches even when most hold nothing, and every request uses the one shared ordering point, so tag-check energy and bus bandwidth scale with N times the miss rate. A directory's traffic tracks the actual sharers, which stays small for lightly shared lines. Its price is a lookup on every miss, extra legs for cache-to-cache transfers (3 with direct data, 4 through H) and per-line metadata that grows with N. At two cores N-1 is one, so the trade-off barely appears.