英特尔 CPU(与所有普通 SMP 系统一样)使用MESI (一种变体)来确保缓存加载/存储的缓存一致性。即所有内核通过它们的缓存看到相同的内存视图。
核心只能在执行读取所有权 (RFO) 后写入缓存行,使该行处于独占状态(没有其他缓存具有可以满足负载的行的有效副本)。相关:原子 RMW 操作通过在操作期间将其锁定在已修改状态来防止其他内核对目标缓存行执行任何操作。
要测试这种重新排序,您需要另外两个线程,它们都读取两个存储(以相反的顺序)。 您提出的方案有一个核心(reader2)在另一个核心(reader1)读取 writer1 存储的同一行的新值之后从内存(或 L3,或其自己的私有 L2/L1)读取旧值。这是不可能的:要让 reader1 看到 writer1 的存储,writer1 必须已经完成了一个 RFO,该 RFO 使任何地方的缓存行的所有其他副本都无效。并且不允许直接从 DRAM 读取而不(有效地)窥探任何回写缓存。(维基百科的 MESI 文章有图表。)
当存储提交(从内核内部的存储缓冲区)到 L1d 缓存时,它同时对所有其他内核全局可见。 在此之前,只有本地核心才能“看到”它(通过存储缓冲区的存储-> 加载转发)。
在数据从一个核心传播到另一个核心的唯一方法是通过全局缓存一致性域的系统上,MESI 缓存一致性单独保证存在单个全局存储顺序,所有线程都可以达成一致。x86 强大的内存排序规则使这种全局存储顺序成为程序顺序的某种交错,我们称之为 Total Store Order 内存模型。
x86 的强内存模型不允许 LoadLoad 重新排序,因此加载按程序顺序从缓存中获取数据,而读取线程中没有任何屏障指令。1
在从相干缓存中获取数据之前,负载实际上会窥探本地存储缓冲区。 这就是您引用的一致顺序规则排除了任一存储由执行负载的同一核心完成的情况的原因。有关加载数据真正来自何处的更多信息, 请参阅全局不可见加载说明。但是当加载地址不与任何最近的存储重叠时,我上面所说的适用:加载顺序是从共享的全局一致缓存域中采样的顺序。
一致的顺序规则是一个非常薄弱的要求。许多非 x86 ISA 并不能在纸面上保证这一点,但很少有实际的(非 x86)CPU 设计具有一种机制,通过该机制,一个内核可以在所有内核全局可见之前查看来自另一个内核的存储数据。IBM POWER with SMT 就是这样一个例子:在不同线程中对不同位置的两次原子写入是否总是以相同的顺序被其他线程看到?解释了一个物理核心内的逻辑核心之间的转发如何导致它。(这与您提出的类似,但在存储缓冲区而不是 L2 中)。
具有超线程(或 AMD 在 Ryzen 中的 SMT)的 x86 微架构通过在一个物理内核上的逻辑内核之间静态分区存储缓冲区来满足这一要求。 在一个带有 HT 的 Core 上执行的线程之间的数据交换将使用什么? 因此,即使在一个物理核心中,存储也必须提交到 L1d(并成为全局可见),然后另一个逻辑核心才能加载新数据。
不将一个逻辑核心中的已退休但未提交的存储转发到同一物理核心上的其他逻辑核心可能更简单。
(x86 的 TSO 内存模型的其他要求,例如按程序顺序出现的加载和存储,更难。现代 x86 CPU 执行乱序,但使用内存顺序缓冲区来维持错觉,并将存储按程序顺序提交给 L1d。负载可以推测性地取值早于“应该”,然后再检查。这就是英特尔 CPU 具有“内存顺序错误推测”管道核弹的原因: 生产者-消费者共享的延迟和吞吐量成本是多少超兄弟姐妹与非超兄弟姐妹之间的记忆位置?。)
正如@BeeOnRope 指出的那样,HT 和保持没有 LoadLoad 重新排序的错觉之间存在交互:通常,CPU 可以检测到另一个内核何时在负载实际读取缓存行之后但在架构上允许读取缓存行之前检测到它:加载端口可以跟踪该缓存行的失效。但是对于 HT,加载端口还必须窥探其他超线程提交到 L1d 缓存的存储,因为它们不会使行无效。(其他机制是可能的,但如果 CPU 设计人员想要“正常”负载的高性能,这是他们必须解决的问题。)
脚注 1:在弱排序 ISA 上,您将使用加载排序障碍来控制每个读取器中的 2 个加载从全局一致的缓存域中获取数据的顺序。
编写器线程每个只做一个存储,所以栅栏是没有意义的。因为所有核心共享一个一致的缓存域,所以栅栏只需要控制核心内的本地重新排序。每个核心中的存储缓冲区已经尝试尽快使存储在全局范围内可见(同时尊重 ISA 的排序规则),因此屏障只会让 CPU 在执行后续操作之前等待。
x86lfence
基本上没有内存排序用例,sfence
仅对 NT 存储有用。只有mfence
当一个线程正在写东西然后读取另一个位置时,才对“正常”的东西有用。 http://preshing.com/20120515/memory-reordering-caught-in-the-act/。因此它会阻止 StoreLoad 重新排序和跨屏障存储转发。
根据@Benoit 的回答,提出以下问题:因此,L1 和 L2 的唯一目的是加速负载。加速存储的是 L3。是对的吗?
不,L1d 和 L2 是回写式缓存: 英特尔酷睿 i7 处理器中使用了哪种缓存映射技术?. 重复存储到同一行可以被 L1d 吸收。
但是 Intel 使用的是包容性的 L3 缓存,那么一个内核中的 L1d 怎么会有唯一的副本呢?L3 实际上是包含标签的,这就是 L3 标签作为窥探过滤器所需的全部内容(而不是向每个核心广播 RFO 请求)。脏行中的实际数据对于每个内核的内部缓存是私有的,但是 L3 知道哪个内核具有该行的当前数据(因此当另一个内核想要读取另一个内核在 Modified 中具有的行时,该向哪里发送请求状态)。干净的高速缓存行(处于共享状态)包含 L3 的数据,但写入高速缓存行不会直写到 L3。