MESI 缓存行中原子存储的语义

Semantics of atomic stores in MESI cachelines

在并发读取和写入行中(仅读取和存储)。当一行在修改或读取模式下由核心拥有时会发生什么,以及一些其他核心问题存储在该行上的操作(假设这些读取和写入是 std::atomic::load 和 std::atomic::store 与 C++ 编译器) ?该线路是否被拉入发出写入的另一个核心?还是写入会根据需要直接找到读取核心?两者的区别在于后者只造成一次读取线路值的往返开销。并且也可能得到并行化(如果存储和读取发生在交错的时间点)

在考虑 NUMA 在并发应用程序中的后果时出现了这个问题。但是,当涉及的两个内核位于同一个 NUMA 节点中时,问题就来了。


其中有大量架构。但就目前而言,对 Intel Skylake 或 Broadwell 上发生的事情感到好奇。

首先,原子 loads/stores 与常规存储在编译为 asm 时没有什么特别之处。 (虽然默认的 seq_cst 内存顺序可以编译为 xchg,但 mov+mfence 也是一个有效的(通常较慢)选项,在 asm 中与普通版本无法区分存储后跟一个完整的屏障。)xchg 是一个原子 RMW + 一个完整的屏障。编译器将其用于全屏障效果;交换的负载部分只是一个不需要的副作用。

此答案的其余部分完全适用于任何 x86 asm 存储,或内存目标 RMW 指令的存储部分(无论它是否是原子指令)。


最初,之前一直在执行写入操作的核心将在其 L1d 中拥有处于 MESI 修改状态的行,前提是它尚未被逐出到 L2 或 L3。

线路更改 MESI 状态(共享)以响应读取请求,或者对于存储,执行写入的核心将发送 RFO(所有权请求)并最终使线路处于修改或独占状态。

在现代英特尔 CPU 的物理核心之间获取数据总是涉及写回共享 L3(不一定是 DRAM)。我认为即使在两个内核位于不同插槽上的多插槽系统上也是如此,因此它们并不真正共享一个公共 L3,使用侦听(和侦听过滤)。

英特尔使用 MESIF。 AMD 使用 MOESI,它允许直接在内核之间直接共享脏数据,而无需首先写回 to/from 共享的外部缓存。

有关详细信息,请参阅


存储数据无法到达另一个核心,除非通过 cache/memory。

您关于在另一个核心上写入 "happening" 的想法并不是任何工作原理。在遵守 x86 内存排序规则的同时,我什至看不出它是如何实现的:来自一个核心的存储在程序顺序中变得全局可见。我看不出如何将存储(到不同的缓存行)发送到不同的内核,并确保一个内核等待另一个内核将这些存储提交到它们各自拥有的缓存行。

即使对于弱排序的 ISA,它也不太合理。通常,当您读取或写入缓存行时,您将进行更多的读取+写入操作。通过核心之间的网状互连分别发送每个读取或写入请求将需要许多微小的消息。高 吞吐量 比低 延迟 更容易实现:更宽的总线可以做到这一点。低延迟加载对于高性能来说是必不可少的。如果线程曾经在核心之间迁移,突然之间它们将成为 read/writing 缓存行,这些缓存行在其他核心的 L1d 中都是热的,这将是可怕的,直到 CPU 以某种方式决定它应该迁移访问它的核心的缓存行。

L1d 缓存体积小、速度快且相对简单。相对于彼此排序内核的读+写以及进行推测加载的逻辑都在单个内核内部。 (存储缓冲区,或者在 Intel 上实际上是一个内存顺序缓冲区,用于跟踪推测加载和存储。)


这就是为什么你甚至应该避免 接触 共享变量,如果你能证明你不必这样做的话。 (或者在适当的情况下使用指数退避)。以及为什么 CAS 循环应该在尝试 CAS 之前以只读方式旋转以等待查看其寻找的值,而不是通过失败 lock cmpxchg 次尝试写入缓存行。