CS152 · 第 21 课

Cache Coherence:多核如何看到一致的内存

单核性能提升在 2004 年前后趋于平缓(时钟频率、单线程性能都撞上了功耗墙),处理器转向多核。这一课直面多核带来的新问题:每个核心都有自己的私有缓存时,怎么保证所有核心看到的内存"故事"是一致的?

授课:Sophia Shao 预计用时:60 分钟 难度:较难

一致性(Coherence)与相合性(Consistency):两个不同的问题

一致性(Coherence):一次读操作能返回什么值?——只关心对单个内存位置的读写。相合性(Consistency):写入什么时候对读者可见?——关心对多个内存位置的读写顺序。这一课先解决一致性问题,第 22 课再处理相合性。

缓存一致性问题的具体场景:Core1 读取 X(缓存了值 2),Core2 写入 X(自己的缓存变成 3),此时主存里仍然是旧值 2——如果 Core1 之后再读 X,如果直接信任自己缓存里的旧值 2,就会得到错误答案。


窥探式缓存(Snoopy Cache, Goodman 1983)

核心思路:"让缓存监听(snoop)其他缓存在内存总线上的事务,然后做出正确的反应"。硬件上,缓存的标签阵列是双端口的:一个端口服务本地处理器,另一个只读端口挂在内存总线上,专门监听总线上的其他事务。协议的两条基本规则:写缺失——在真正执行写操作之前,先让该地址在所有其他缓存中失效;读缺失——如果在某个缓存中发现了脏(dirty)副本,先把它写回内存,再执行读取。


MSI 协议:三个状态

状态转移的核心:I→M(写缺失)、I→S(读缺失,从内存取得该行)、S→M(本处理器打算写入)、M→S(其他处理器读取,本处理器先写回)、M→I(其他处理器打算写入,本处理器先写回)、S→I(其他处理器打算写入)。关键观察:任何进入 M 状态的路径都要求独占——这正是保证内存整体一致的根本机制。

MESI 在 MSI 基础上新增了 E(Exclusive,独占但未修改)状态:如果读缺失发生时没有其他共享者,直接进入 E 而不是 S;此后如果本处理器要写入,E→M 可以静默完成,不需要产生任何一致性流量——这正是 MESI 相对 MSI 的性能优化:常见的"读后立即写"(read-modify-write)模式,如果数据本来就是私有的,就不必先经过 S 状态再触发一次多余的一致性广播。


用二级缓存优化窥探开销

如果 L1/L2 满足包含性质(L1 的内容必须也在 L2 中),窥探逻辑就只需要挂在 L2 上:L2 缺失就意味着 L1 里一定也没有;只有当某次失效命中了 L2,才需要进一步探测并使 L1 中对应的行失效——这样"在总线上窥探"这件事完全不占用 CPU-L1 之间的宝贵带宽。干预(Intervention):当一个缓存持有脏(已修改)数据,而另一个处理器发生读缺失时,即使主存本身返回的是过期数据,也必须由持有脏数据的那个缓存通过内存控制器把正确的数据"插话"提供出来。


假共享(False Sharing):缓存行粒度带来的意外代价

一个缓存行通常包含不止一个字,而一致性协议是按行粒度维护的,不是按字粒度。假共享发生在两个处理器写入不同的字、但这些字恰好落在同一个缓存行里——这会触发本来不必要的失效流量。区分的关键测试:真共享缺失——即使缓存行只有 1 个字,这次缺失依然会发生(真实存在数据依赖);假共享缺失——如果缓存行只有 1 个字,这次缺失就不会发生(纯粹是行粒度造成的误伤)。这正是缓存缺失的"第四个 C":一致性缺失(Coherence miss),与强制性、容量性、冲突性缺失并列。

真共享 vs. 假共享判定实例(MSI 协议,重新核对):假设 x1 和 x2 位于同一缓存行,P1、P2 都曾经读过这行:

时刻P1P2判定
1写 x1真共享缺失:使 P2 中 x1 失效
2读 x2假共享缺失:x1 与 x2 无关
3写 x1假共享缺失:x1 与 x2 无关
4写 x2真共享缺失:x2 本身不可写
5读 x2真共享缺失:P1 中 x2 已失效

逐行核对 MSI 状态转移:T1 时 P1 写 x1 触发 S→M(P1)和 S→I(P2)——这是围绕 x1 本身的真实数据依赖,判定真共享;T2 时 P2 的行早已因 T1 失效,读 x2 触发缺失,但 x2 从未被 P1 碰过,判定假共享;T3 同理,P1 再次写 x1,与 x2 无关,假共享;T4 时 P2 写 x2,这次是围绕 x2 本身的真实写操作,判定真共享;T5 时 P1 读 x2,而 x2 恰好在 T4 被 P2 真正修改过,P1 的副本因此失效,判定真共享。逐行重新推演后,五行判定与幻灯片完全一致——这个例子直观展示了"同一缓存行、同一份失效流量,可能是真依赖,也可能是纯粹的行粒度误伤",取决于当次访问的字是否真的是被写过的那个字。


目录式一致性:突破窥探的带宽瓶颈

窥探协议要求每次访存都向所有缓存广播——核心数增多后,这条广播总线本身就会成为瓶颈("大多数窥探请求根本找不到匹配",纯属浪费带宽)。目录式一致性的思路:每一行内存都关联一份目录信息,记录当前有哪些缓存持有该行的副本、处于什么状态;发生缺失时,先查目录,然后只与真正持有副本的那些节点通信,而不是广播给所有节点。

目录侧的状态(区别于 MESI 命名):R(dir)——被 dir 集合中的站点共享(dir 为空表示没有任何站点缓存该行);W(id)——被站点 id 独占缓存并已修改,内存数据已过期;TR(dir)——瞬态,等待失效请求的确认;TW(id)——瞬态,等待站点 id 把该行数据同步回主存。真实例子:Intel Knights Corner(2012)用环形互联把核心和"标签目录(TD)"/"内存控制器(GDDR MC)"节点串起来,是目录式一致性在真实芯片上的落地。


常见误区


知识检查

问题 1:MESI 协议相对 MSI 协议新增的 E(Exclusive)状态,主要优化了什么场景?

问题 2:如何判断一次缓存一致性缺失是"真共享"还是"假共享"?

问题 3:目录式一致性协议相对窥探式协议,是如何突破"核心数增多后广播带宽成为瓶颈"这个问题的?


本课要点

一致性 vs. 相合性

单地址读写规则 vs. 多地址读写顺序,本课先解决前者

MSI / MESI

M 态保证独占,E 态让私有数据写入无需广播

假共享

行粒度一致性带来的意外代价,用"单字缓存行"思想实验判定

目录式一致性

只与真正持有副本的节点通信,突破窥探式广播的带宽瓶颈


参考资料与延伸阅读