Archipelago

Lec 13目录式缓存一致性(Directory-Based Cache Coherence)

讲义完成更新于 2026-09-29

Lec 13 目录式缓存一致性(Directory-Based Cache Coherence)

MIT 6.5900 Fall 2024 · Daniel Sanchez 主题:目录式 vs 侦听式、MSI 目录协议、MSHR、目录组织与不精确表示、协议竞争与死锁、多级层级与原子操作

TL;DR

  • 目录式可扩展架构:通过目录跟踪私有缓存共享者列表,将全局广播替换为点对点消息,使一致性协议能够扩展至成百上千核。
  • MSI 状态协同与 MSHR:目录维护 Uncached、Shared、Exclusive 三态并处理权限降级与作废;未命中状态保持寄存器(MSHR)并发跟踪在途访存事务。
  • 目录组织与死锁防范:采用稀疏全映射或有限指针等不精确表示平衡面积开销;物理网络上划分请求与响应的独立虚拟网络(Virtual Networks)彻底消除协议死锁。

一、维持一致性的充分条件

只要硬件保证:

  • 任一时刻只有一个处理器对某位置有写权限;
  • 写之后没有处理器能 load 该位置的陈旧副本。

一种正确做法:

  • 写请求:写执行前,在所有其他私有缓存中作废该地址;
  • 读请求:若某私有缓存有脏副本,先写回再读内存。

二、目录式 vs 侦听式 [Censier and Feautrier, 1978]

侦听式(Snoopy)目录式(Directory)
请求方式在内存总线上广播只向可能持有该行的缓存发消息
可扩展性难扩展到大量处理器可扩展到大量处理器
额外开销需对缓存 tag 的额外侦听带宽需额外的目录存储来跟踪可能的共享者

三、MSI 目录协议

  • 缓存状态:M / S / I;

  • 目录状态:

    • Uncached(Un):无共享者;
    • Shared(Sh):一个或多个共享者有读权限(对应 S);
    • Exclusive(Ex):单一共享者有读写权限(对应 M)。
  • 为清晰起见瞬态未画;先假设无竞争请求。

目录每项记录:Tag | State | Sharers(共享者集合)。

缓存端的转换

  • 由处理器访问触发:PrRd/ShReq(I→S)、PrWr/ExReq(I→M 或 S→M);
  • 由目录请求触发:InvReq/InvResp(作废,M 态附带数据、S 态不带数据)、DownReq/DownResp(降级,M→S 附带数据);
  • 由逐出触发:Eviction/WbReq(M 态附带数据写回,S 态不带数据)。

目录端的转换

由数据请求触发:

Un --ShReq--> Sh : Sharers = {P}; ShResp
Sh --ShReq--> Sh : Sharers += {P}; ShResp
Un --ExReq--> Ex : Sharers = {P}; ExResp
Sh --ExReq--> Ex : Inv(Sharers - {P}); Sharers = {P}; ExResp
Ex --ShReq--> Sh : Down(Sharer); Sharers = Sharer + {P}; ShResp
Ex --ExReq--> Ex : Inv(Sharers); Sharers = {P}; ExResp

由写回请求触发:WbReq 时按共享者数更新(>1 则移除该 P,==1 则清空),WbResp。

协议示例要点

  • LD(读):私有缓存发 ShReq,目录置 Sh {P} 并 ShResp 回数据;
  • ST(写)且原为 Sh:发 ExReq,目录向其他共享者发 InvReq,收齐 InvResp 后置 Ex {P} 并 ExResp;
  • 逐出 M 行:发 WbReq(附数据),目录写回主存并置 Un。

一个核先写回 0xA 再请求 0xB,两者会被串行化(受同一 MSHR/通道约束)——可考虑分离资源来并行化。


四、缺失状态保持寄存器(Miss Status Holding Register, MSHR)

MSHR 保存缓存之外的 load 缺失与写。

  • 逐出/写回时:无空闲项则停顿;分配新项;通道可用时发 WBReq 与数据;收 WBResp 后释放;
  • load 缺失时:先在 MSHR 中找匹配地址。未找到则(无空闲项停顿,否则)分配新项;找到则只填入对应 ld/st 槽;发 ShReq(或 ExReq),收响应后把数据转发给 CPU 与缓存,再释放;
  • 每项含多个 ld/st 槽:允许用一个项服务对同一块的多个请求。

五、目录组织

需求:目录要跟踪共享某块的所有核。挑战:每块存共享者列表的空间随可能共享者数增长。

1. 平坦的、基于内存的目录(Flat, Memory-based)

为每行用主存中的若干位存状态与共享者,用位向量编码共享者。

简单;但慢,且处理器多时极低效(约 P 位/行)。

2. 稀疏全映射目录(Sparse Full-Map)

只需跟踪在私有缓存中的行。把目录组织成一个缓存(含 Line Address / State / Sharer Set,多路组相联)。

优点:低延迟、节能。缺点:位向量随核数增长 → 面积扩展差;相联度有限 → 目录诱发的作废。

目录诱发的作废(Directory-Induced Invalidations):为保持包含性(inclusion),把某项复用给新地址前,必须作废该项的所有共享者。

3. 共享者集合的不精确表示

  • 粗粒度位向量(如 1 位代表 4 个核);
  • 有限指针(Limited pointers):只维护少量共享者指针,溢出时标记"全部"并广播(或作废一个共享者);
  • 允许假阳性(如 Bloom filter)。

优点:减少面积与能量。缺点:开销仍不可扩展(只是调常数因子);不精确的共享者 → 广播、作废或多余的作废/降级。


六、协议竞争与延迟优化

协议竞争(Protocol Races)

目录对同地址的多请求串行化(排队或 NACK 重试),但冲突请求仍有竞争。

升级竞争(Upgrade race):缓存 0 与 1 同时对 0xA 发 ExReq;目录先服务 0 的请求、把 1 的排队;缓存 1 本期望 ExResp 却收到 InvReq → 它应从 S→M 转为 I→M 并发 InvResp。

额外跳数与三跳协议(3-Hop)

  • 问题:别的私有缓存中的数据要经目录中转,增加延迟;
  • 优化:让持有者直接把数据转发给请求者(如 ExFwd + ExResp + ExAck),减少跳数。

多级层级中的一致性

  • 可在多级用相同或不同协议;关键不变式:在所有中间层级保证足够的权限;
  • 例:8-socket Xeon E7(每 socket 8 核)—— socket 内用 MESI + L3 内嵌目录,socket 间用 MESIF + 侦听(QPI)。

内嵌缓存目录(In-Cache Directories)

把目录信息嵌入共享末级缓存(LLC)的 tag(共享缓存必须包含私有缓存内容)。

优点:避免独立 tag 开销与单独查找。缺点:当共享缓存 ≫ 私有缓存总和时可能低效。

避免协议死锁

即使网络无死锁,协议也可能死锁(如两节点用请求塞满所有中间缓冲,阻塞响应进入网络)。

解法:分离虚拟网络(不同虚通道与端点缓冲,共享同一物理路由器与链路)。多数协议至少需 2 个虚拟网络(请求与回复),常需 >2。


七、同步与原子操作

实现同步需原子读-改-写(如 swap)。一致性协议会让 mutex 在各核私有缓存间反复乒乓;可用 test&test&set 减少。

实现原子指令的选项

  • 侦听式:锁总线 → 昂贵;
  • 目录式:锁住缓存中的行(原子操作完成前禁止作废/逐出)→ 复杂;
  • 现代处理器用 load-reserve / store-conditional。

Load-reserve & Store-conditional

在目录式分布式共享内存系统中,传统的「总线加锁」原子操作完全失效。现代体系结构普遍采用一对协作指令——保留加载(Load-Reserve, LR) 与 条件存储(Store-Conditional, SC),在无锁总线的前提下通过缓存一致性状态机实现乐观并发原子操作。

硬件为每个处理器核心配备专用的预约寄存器(Reservation Register),包含有效标志位(Valid Flag)与被监控的物理地址(Address)。其原子操作分三步推进:

  1. 发起预约(Load-Reserve):核心读取目标地址的数据写入通用寄存器,同时在硬件预约寄存器中记录该地址并将有效标志位置 1。此时私有缓存只需以共享态(Shared)持有该缓存行即可。
  2. 侦听冲突:在 LR 与 SC 之间的执行窗口内,本地缓存控制器持续监控来自互连网络的一致性消息。一旦收到来自目录的针对该预约地址的作废请求(InvReq)或降级请求(表明其他核抢先获得了独占写权限),硬件自动将预约有效标志位强行清零。
  3. 条件提交(Store-Conditional):核心尝试将新值写入该地址。硬件首先原子校验预约寄存器的状态:若有效标志位仍为 1,则向目录申请独占写权限,完成数据写入并将状态标记为成功(status = succeed);若有效位已被清零,则直接放弃写入并将状态标记为失败(status = fail),由软件循环重新尝试。
Load-reserve R, (a):           Store-conditional (a), R:
    <flag, adr> ← <1, a>           if <flag, adr> == <1, a>
    R ← M[a]                           then 取消其他处理器对 a 的预约; M[a] ← R; status ← succeed
                                       else status ← fail

通过这一硬件机制,自旋锁等同步原语可被精简重构为纯粹的重试循环:

# Swap(R1, mutex):
L: Ld-Reserve R2, (mutex)
   St-Conditional (mutex), R1
   if (status == fail) goto L
   R1 <- R2

相比于传统原子指令,LR/SC 实现了读写解耦:所有竞争获锁的核在自旋阶段均以共享只读方式读取锁变量,绝不触发写失效广播;只有在锁被释放时才会有核心执行 SC 尝试获取独占权,彻底消除了无谓的缓存行乒乓与互连网络带宽饱和。


目录式缓存一致性与协议设计总结

  • 目录式一致性只向相关私有缓存发消息,以额外目录存储换取可扩展性,目录是排序点,支持无序网络;
  • MSI 目录协议用目录状态 Un/Sh/Ex 与共享者集合,配 InvReq/DownReq/WbReq 等消息;MSHR 管理在途缺失与写;
  • 目录组织在面积与精度间权衡:平坦位向量 → 稀疏全映射 → 不精确表示(粗粒度/有限指针/Bloom);
  • 需处理协议竞争(如升级竞争)、用三跳转发降延迟、分离虚拟网络避死锁;原子操作用 LR/SC 实现。

在构建多级目录式缓存系统时,末级缓存(LLC)与私有 L1/L2 缓存之间的包含性属性(Inclusion Property)是架构师必须面临的核心决断:

  • 严格包含(Strict Inclusion):LLC 必须容纳所有私有 L1/L2 中的行,此时目录信息可以直接嵌入在 LLC 的 Tag 阵列中,消除了独立目录的面积与访问开销。然而其致命代价在于「逆向作废(Back Invalidation)」——当 LLC 发生容量冲突需要逐出某行时,必须向私有 L1/L2 强行发送作废消息;一旦多核并发工作集庞大,LLC 的相联度瓶颈将引发剧烈的跨核颠簸,严重杀伤私有缓存命中率。
  • 非包含 / 独占(Non-Inclusive / Exclusive):如现代 AMD Zen 架构,LLC 并不强制包含私有 L2。虽然避免了逆向作废,但系统必须配备独立的稀疏目录(Sparse Directory)或探测过滤网(Probe Filter),其表项容量必须足以覆盖全部私有缓存行。若稀疏目录发生冲突逐出,同样会触发目录诱动作废(Directory-Induced Invalidation)。因此,包含性选择本质上是在「Tag 硅片面积开销」与「跨层级作废流量」之间进行的一场高难度微架构博弈。 :::

下一讲:一致性与松弛内存模型