L12:缓存一致性(Cache Coherence)
MIT 6.5900 Fall 2024 · Daniel Sanchez 主题:多核动机与 Amdahl 定律、通信模型、一致性 vs 一致、侦听式协议(VI/MSI/MESI/MOESI)、同步与原子操作
一、多核与可扩展性
- 自 2005 年起,系统性能提升主要来自每芯片核数增加(技术缩放 + ILP 受限);
- 多核性能的限制因素:应用并行度有限、访存与核间通信、编程复杂度。
Amdahl 定律
其中
推论:让常见情况快(make the common case fast)。 例:90% 可并行、10% 串行,则
。要用好 1000 核,需要极高的 。
二、通信模型
| 模型 | 地址空间 | 通信方式 | 低层编程模型 |
|---|---|---|---|
| 共享内存(Shared Memory) | 单一地址空间 | 隐式:读写内存(数据 + 控制如信号量/锁/屏障) | 线程 |
| 消息传递(Message Passing) | 各自独立地址空间 | 显式:send/recv 消息 | 进程 + 进程间通信(如 MPI) |
三、一致性 vs 一致(Coherence vs Consistency)
共享内存系统为性能设多个私有 Cache,需提供"单一共享内存"的假象。直觉:读应返回最近写入的值——但"最近"如何定义?
- 一致性(Coherence):读能返回哪些值?——关注单个内存位置的读写;
- 一致(Consistency):写何时对读可见?——关注多个内存位置的读写。
一致性协议的两条规则
- 写传播(Write propagation):写最终对所有处理器可见;
- 写串行化(Write serialization):对同一位置的写被串行化(所有处理器看到相同顺序)。
保证写传播的方式:
- 写失效(Write-invalidate):写之前作废其他所有缓存副本;
- 写更新(Write-update):写之后更新其他所有缓存副本。
跟踪共享状态并串行化请求的方式:
- 侦听式(Snooping-based):所有 Cache 通过共享总线观察彼此动作(总线是串行化点);
- 目录式(Directory-based):一致性目录跟踪私有 Cache 内容并串行化请求(目录是串行化点)。
四、侦听式一致性(Snooping)[Goodman, 1983]
各 Cache 监听总线以保持各处理器的内存视图一致。总线提供串行化点(广播、全序)。每个核的 Cache 控制器侦听所有总线事务,响应来自核与总线的请求、改变所选缓存块状态、产生总线事务。协议用有限状态机(FSM)描述。
1. 简单协议:有效/无效(VI)
假设写穿透(writethrough)Cache。状态转换记法:触发动作 / 采取的动作。
- 动作:处理器读 PrRd、处理器写 PrWr、总线读 BusRd、总线写 BusWr;
- Valid:
PrRd/--、PrWr/BusWr、BusWr/--→Invalid; - Invalid:
PrRd/BusRd→Valid、PrWr/BusWr→Valid。
VI 的问题:每次写都更新主存,每次写都需广播 + 侦听。
2. 修改/共享/无效(MSI)
允许写回(writeback)Cache + 本地满足写。
- 状态:M(Modified,已修改独占脏)、S(Shared,共享只读)、I(Invalid);
- 新动作:总线读独占 BusRdX、总线写回 BusWB;
- 关键转换:写时发
BusRdX获得独占进入 M;M 态被其他核 BusRd 时需BusWB写回降为 S;M 态被 BusRdX 时写回后作废。
MSI 相比 VI 的优势:核 0 进入 M 后,其后续的读和写都可本地满足,无需总线事务。
Cache 干预(intervention):MSI 允许 Cache 不更新主存就服务写,故主存可能有陈旧数据。当另一核请求时,持有脏数据的 Cache 必须供数据并覆盖主存的响应。
3. MSI 优化:独占态 E → MESI
- 观察:对私有数据做读-改-写序列很常见,MSI 中读会进入 S,写还需一次 BusRdX;
- 方案:加 E 态(Exclusive,独占且干净)。若无其他共享者,读直接获得 E 而非 S;写则静默地 E→M(无需总线事务)。
MESI:M(已修改独占)、E(独占未修改)、S(共享)、I(无效),提升私有读写数据性能。
4. MSI 优化:拥有态 O → MOSI / MOESI
- 观察:M→S 转换必须写回;频繁读写共享时开销大,能否把写回推迟到 S 之后?
- 方案:加 O 态(Owner)= S + 写回责任。M→S 时,一个共享者(通常原持 M 者)保留为 O 而非 S;逐出时由 O 写回(或另一共享者 S→O)。
经验:私有读写 ≫ 共享只读时常用 E;写回昂贵(主存 vs L3)时才用 O。
五、总线与非原子性
分裂事务与流水总线
- 原子事务总线:简单但吞吐低;
- 分裂事务总线(Split-Transaction):支持多个并发事务(吞吐高),响应可能乱序到达,常实现为多条总线(请求 + 响应)。
非原子性 → 瞬态(Transient)状态
协议须处理缺乏原子性的情况,状态分两类:
- 稳定态(如 MSI);
- 瞬态(如 I→S、I→M、S→M):因分裂 + 竞争而出现,更复杂。
扩展一致性的难题
可实现比总线扩展性更好的有序互连,但广播本质上不可扩展(数百次 Cache 侦听的带宽与能量)。
例:Starfire E10000 —— 一致性请求单播到根处串行化,再广播下发到所有处理器。 出路:目录式一致性——所有事务经目录路由,跟踪私有 Cache 内容(无广播),作为冲突请求的排序点(支持无序网络)。
六、一致性带来的性能问题
问题 1:伪共享(False Sharing)
一个缓存块含多个字,一致性在块级而非字级进行。若 P1 写
问题 2:同步
一致性协议会让 mutex 在各核 Cache 间 ping-pong。可用 test&test&set(先非原子读 mutex,仅当为 0 才执行 swap)减少 ping-pong。
问题 3:总线占用
- 原子读-改-写指令一般需两次内存(总线)操作且中间不能插入其他处理器的内存操作;
- 多处理器下须在整个原子读写期间锁总线 → 对简单总线昂贵、对分裂事务总线极昂贵;
- 现代处理器改用 load-reserve / store-conditional。
Load-reserve & Store-conditional
用特殊寄存器保存预约标志、地址与 store-conditional 的结果:
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侦听器若看到对预约寄存器地址的 store 事务,就把预约位清 0。多个处理器可同时预约 a;这对总线流量而言就像普通 load/store。 性能:内存事务总数不必然减少,但拆分原子指令可提高总线利用率(尤其分裂事务总线)、减少 Cache ping-pong(尝试获锁的处理器不必每次都 store)。
小结
- 多核性能受 Amdahl 定律与通信约束;共享内存系统需一致性协议维持"单一内存"假象;
- 一致性管单地址读写、一致管多地址可见时序;写传播靠失效/更新,串行化靠侦听/目录;
- 侦听式协议从 VI → MSI → MESI(加 E 省私有读写)→ MOESI(加 O 推迟写回)逐步优化;
- 广播不可扩展 → 引出目录式;伪共享、同步 ping-pong、总线占用是三大性能问题,load-reserve/store-conditional 是现代原子操作方案。
下一讲:目录式缓存一致性