CS149 笔记:Cache Coherence 与 Memory Consistency
这两节课讨论的是共享内存系统中两个相邻但不同的问题:多个缓存副本如何保持一致,以及不同内存操作可以按什么顺序被观察到。
简单地说,coherence 管同一个地址,consistency 管不同地址之间的顺序。前者主要由缓存协议完成,后者则需要硬件内存模型与同步原语共同约束。
Cache Coherence
缓存为什么需要一致性协议
多核处理器会把同一块内存复制到各个核心的私有缓存中。write-back cache 又允许修改后的数据暂时只存在于某个缓存里,因此不同核心可能持有同一地址的不同版本。
缓存一致性要求:对于每个内存地址,所有处理器都同意它的写入顺序,读取结果等价于读到该顺序中最近一次写入的值。这里的“最近”指系统认可的逻辑顺序,而不是物理时间。
协议需要维持两个基本不变量:
- SWMR(Single-Writer, Multiple-Reader):同一时刻只能有一个可写副本,或者存在多个只读副本。
- Data-Value invariant:写入阶段产生的最新值必须正确传递给之后的读取者,不能重新读到旧版本。
MSI 与 MESI
MSI 为每条 cache line 维护三种状态:
M(Modified):当前缓存独占该行并已修改,内存可能不是最新值。S(Shared):一个或多个缓存持有只读副本,内存是最新值。I(Invalid):当前副本不可使用。
处理器只有取得 M 状态后才能写入。BusRd 用于请求可读副本,BusRdX 用于请求独占副本并使其他副本失效,BusWB 则负责交出或写回脏数据。
图中绿色路径由本地读写触发,红色路径由其他缓存的总线请求触发。MSI 的关键不是记住每条箭头,而是理解:写入前必须获得独占权,拥有最新脏数据的缓存必须负责把它交给下一位读取者。
MESI 在 MSI 上增加了 E(Exclusive)状态。E 表示当前缓存拥有唯一但尚未修改的副本,因此可以在本地直接升级为 M,省去一次总线事务。它优化的是“数据实际没有被共享,却仍要先读后写”的常见情况。
Snooping 与 Directory
Snooping 协议把一致性请求广播给所有缓存控制器。总线天然提供统一的事务顺序,设计直观,但广播成本会随着核心数增加。
Directory 协议为每条 cache line 记录 sharer 和 owner,只向真正持有该行的缓存发送点对点消息。它减少了无关广播,更适合多核和 NUMA 系统,但需要额外的目录空间与查询过程。
Directory 改变的是寻找副本和传递消息的方式,SWMR 与写入序列化这两个目标并没有改变。
False Sharing
一致性协议通常以 cache line 为粒度。即使线程修改的是不同变量,只要这些变量落在同一条 cache line 中,写入仍会导致整行在核心之间反复转移,这就是 false sharing。
它不会破坏正确性,却会制造本来不存在的通信。常见处理方式是分离高频写入的线程私有数据、按 cache line 对齐,或者先在本地累计再统一合并。是否需要 padding 应通过性能测试确认,因为过度填充也会扩大工作集。
Memory Consistency
它与 Coherence 的区别
Coherence 只保证单个地址的行为合理,并不保证不同地址的操作按照源码顺序被其他处理器观察。
Memory consistency model 是硬件或语言与程序之间的契约,规定四类顺序中的哪些必须保留:W→R、R→R、R→W 和 W→W。
Sequential Consistency
顺序一致性(SC)要求所有内存操作能够排列成一个全局串行顺序,并且每个线程在其中仍保持自己的程序顺序。它给出的模型最接近“所有处理器轮流访问同一份内存”,因此最容易推理。
SC 会限制硬件隐藏内存延迟的能力。现代处理器使用 write buffer、乱序执行和非阻塞缓存,让彼此独立的内存操作重叠进行,因此实际系统通常采用更宽松的模型。
Relaxed Consistency
不同模型的核心差异在于允许放松哪些顺序:
- TSO 主要放松
W→R,后续读取可以越过尚未全局可见的写入。 - PSO 进一步允许不同地址的写入重排,即放松
W→W。 - Weak / Release Consistency 允许普通数据操作更广泛地重排,在同步边界处恢复必要顺序。
放松顺序并不是破坏 coherence。每个地址仍然可以拥有一致的写入序列,只是不同地址的操作可能以不同于程序顺序的方式对外可见。
Fence、同步与 Data Race
Fence 用来阻止特定内存操作跨越边界重排。它能恢复弱内存模型没有提供的顺序,但也会限制处理器并行隐藏延迟的空间,因此通常应通过锁、barrier 和语言级原子操作间接使用。
当两个线程访问同一位置、至少一个是写,并且这些访问没有被同步关系排序时,就产生了 data race。
现代语言的重要保证是 SC for DRF:只要程序没有 data race,就可以按照顺序一致的结果进行推理,编译器与同步库负责适配底层硬件的弱内存模型。在 C/C++ 中,普通对象上的 data race 属于 undefined behavior;volatile 不能代替原子操作或线程同步。
两节课之间的关系
Coherence 解决缓存副本的正确性,consistency 定义内存操作的可见顺序。硬件先通过 MSI、MESI 或 Directory 保证每个地址不会出现互相矛盾的版本,再通过内存模型规定不同地址之间允许怎样重排;程序则使用同步原语补上正确性所需的顺序约束。
这也是理解共享内存程序的基本视角:coherence 让数据副本可信,consistency 让操作顺序可推理。