一致性协议和存储缓冲区

问题描述 投票:0回答:1

考虑下面的代码:

std::atomic<int> a = 100;
---
CPU 0:
a.store(101, std::memory_order_relaxed);
---
CPU 1:
int tmp = a.load(std::memory_order_relaxed);  // Assume `tmp` is 101.

假设 CPU 0 碰巧在 CPU 1 加载

a
之前更早地存储到
a
(无论加载是否被重新排序)。因此,在这种情况下,
tmp
将是 101 而不是 100。

如果使用 MOESI 一致性协议,则当 CPU 0 存储到

a
时,CPU 0 以修改(M)模式获取缓存行。存储进入 CPU 0 的存储缓冲区。如果 CPU 1 在其自己的高速缓存中具有高速缓存行,则其高速缓存行的副本将转换为无效 (I) 模式。

当 CPU 1 加载时

a
,缓存线将转换为共享 (S) 模式(或者可能是拥有 (O) 模式)。

假设当CPU 1加载

a
时,
a
仍在CPU 0的存储缓冲区中。
假设CPU 1无法读取CPU 0的存储缓冲区,那么当CPU 1使用
a
读取缓存行时,这是否意味着CPU 0 的存储缓冲区已刷新(或者至少,具有
a
的缓存行从 CPU 0 的存储缓冲区中刷新)?

如果刷新没有发生,则意味着 CPU 0 和 CPU 1 都具有共享 (S) 模式的缓存行,但 CPU 0 看到

a
的值为 101,而 CPU 1 看到
a
的值为值为 100。

注意:我问的是 MOESI,而每个微架构都实现了自己的一致性协议。我想在大多数微架构中都会以类似的方式处理这个问题。

c++ x86 cpu-architecture atomic memory-barriers
1个回答
0
投票

存储缓冲区不会被其他核心的负载监听;他们是私人的。当存储从存储缓冲区提交到 L1d 缓存时,存储将变得全局可见。 (核心必须先获得该行的 MESI 独占所有权,然后才能做到这一点,E 或 M 状态。) 这必须等到存储指令完成后,也就是从 ROB(重新排序缓冲区)中退出,所以它是非推测性的。

存储缓冲区是允许存储的推测执行所必需的

,其中包含该核心的推测状态,如果检测到错误推测(例如分支错误预测或较早指令中的错误),则可能需要回滚该推测状态。

在其自己的存储变得全局可见(对任何其他核心)之前,核心可以看到自己的存储(通过存储转发)。

这种“重新排序”在某种程度上与存储缓冲区在以后的加载不同时引入的通常的 StoreLoad 重新排序不同。地址。 另请参阅全局不可见加载说明有关它的一些讨论。 (还有一些有趣的极端情况,比如负载与商店“部分”重叠,看到的值是其他核心无法看到的。) x86 的 TSO 内存模型是具有存储缓冲区 + 存储转发1的程序顺序,用于每个核心对一致共享缓存的访问。 (参见 Preshing 的类比,

内存屏障就像源代码控制操作

。)提及存储转发很重要,因为如果“命中”存储缓冲区中已有地址的加载刚刚停止,它会产生您不会看到的效果直到存储缓冲区提交到缓存。 在存储可以提交到L1d(并变得全局可见)之前,缓存行必须是独占的,但是如果没有这样,存储转发到该核心自己的负载也可以发生。

(在大多数架构上,致力于 L1d 和 MESI 一致性是存储在当前核心之外可见的唯一方法。但是 PowerPC 允许将“分级”存储转发到其他逻辑 SMT 核心,使 IRIW 重新排序成为可能 .)

脚注 1

:在 x86 内存模型真正被记录之前,这是 486 或 P5 Pentium“自然”所做的事情,具有有序管道和存储缓冲区。 P6 煞费苦心地没有引入任何新的内存重新排序,以避免破坏现有的多线程代码。它会“推测性”地提前加载,但如果它检测到缓存行在实际加载时和架构上允许加载时之间已失效,则会通过内存顺序错误推测管道核弹进行回滚。

© www.soinside.com 2019 - 2024. All rights reserved.