# Cache directory protocols **Directory-based protocols** implement [[cache-coherence]] without relying on a shared broadcast bus. Instead of every cache snooping every transaction, a **directory** explicitly tracks, for each cache line, which cores currently hold a copy and in what state. A core that wants to write to a line consults the directory, which tells it exactly which other cores (if any) need to be invalidated, and messages only those cores instead of broadcasting to all of them. ## Why bother, given snooping works [[cache-snoopy-protocols|Snoopy protocols]] depend on every cache seeing every coherence-relevant transaction, which requires a bus (or bus-equivalent) that provides total ordering. That works fine for a handful of cores, but bus bandwidth is a shared, fixed resource: every additional core adds snoop traffic that every other core has to filter, whether or not it cares about the address in question. Past roughly a few dozen cores or across multiple sockets, this becomes the bottleneck. A directory replaces "ask everyone" with "ask the directory, then only the parties involved," so message traffic scales with actual sharing rather than with total core count. ``` Snooping: every write -> broadcast to all N caches Directory: every write -> lookup directory -> message only the K actual sharers (K <= N) ``` ## Where the directory lives The directory can be centralized (one structure tracking every line in the system, simple but a potential bottleneck and single point of contention) or distributed, where each memory bank or [[l3-cache|LLC]] slice owns the directory entries for the addresses it's responsible for. Distributed directories are the norm on large multi-socket and [[numa]] systems, since they let directory lookups stay local to the node that owns the memory in question for the common case of node-local access. ## Cost of the directory itself Directory state has to live somewhere, and a naive full-map directory (one bit per core per line, recording who holds it) grows linearly with core count, which becomes expensive at scale. Real implementations use compressed representations (tracking a bounded number of sharers explicitly and falling back to a broadcast for the rare case of wider sharing, or using a coarser sharing vector) to keep directory overhead sublinear in core count while still avoiding a full broadcast on the common case.