首页 > 解决方案 > X86 线性化?

问题描述

X86 不提供开箱即用的顺序一致性 (SC)。

X86提供TSO;因此它将免费提供以下障碍

[LoadLoad]
[LoadStore]
[StoreStore]

常规加载提供获取语义。

r1=A
[LoadLoad]
[LoadStore]
...

常规商店提供发布语义。

...
[StoreStore]
[LoadStore]
X=r1

因此,用于常规加载和存储的 X86 提供了获取/释放语义。

这对于 SC 来说是不够的,例如

[StoreStore]
[LoadStore]
X=r1
r2=Y
[LoadStore]
[LoadLoad]

在这种情况下,存储和加载仍然可以重新排序,因此它不是 SC。为了解决这个问题,可以添加一个 [StoreLoad] 屏障(例如 MFENCE)。

[StoreStore]
[LoadStore]
X=r1
[StoreLoad]<--
r2=Y
[LoadStore]
[LoadLoad]

所以现在我们已经从获取/释放语义升级到了 SC。

在大多数情况下,读取比写入更频繁,因此对写入执行 [StoreLoad] 是最有益的。

[StoreStore]
[LoadStore]
X=r1
[StoreLoad]

我的问题是关于线性化的。线性化和 SC 之间的区别在于,对于 SC,操作的效果可以在调用开始之前或调用完成之后倾斜,但是对于线性化,要求调用的效果在调用开始和调用完成之间。

这让我产生疑问;X86 可以提供线性化吗?

让我们首先确定调用开始和完成:

调用开始:指令的发出;因此,当 ROB 上的条目被保留时。

调用完成:删除 ROB 的指令(例如,在存储的情况下,当项目从 SB 移动到 L1D 时)。

当从缓存或内存中读取数据时,负载将成为全局可见的。这是在开始之后和完成之前。MESI 协议将防止负载读取过时的值。

当商店离开 SB 并到达 L1d 时,商店将成为全局可见的。这也在调用开始和完成之间。

所以在我看来,X86 可以提供线性化。

标签: x86memory-barriersconsistency

解决方案


商店从 ROB 退休不会承诺 L1d 。这将不必要地将执行绑定到提交,从而失去隐藏偶尔缓存未命中存储的一些好处。(这个好处甚至适用于有序 CPU。)

当存储从 ROB 中退出时,存储缓冲区条目“毕业”并成为提交到 L1d 的候选者。提交不能在退休之前发生。它发生在一段时间后,一旦它到达 SB 队列的头部(在 x86 上,提交按程序顺序)。提交到 L1d 是它变得全局可见的时刻。

(存储缓冲区总是尽可能快地将自身排入 ROB。 mfence或者locked 指令只是让这个内核在执行以后的加载之前等待这种情况发生。)

如果我正确理解了您对“可线性化”的定义,那么除了内存障碍之外,您还需要额外的障碍来提供它。


lfence在乱序的后端序列化执行(在以后的指令发出之前耗尽 ROB),所以mfence+lfence我可以认为通过在要保持完全分离的两条指令之间设置这样的屏障来完全序列化执行 + 内存提交。(例如,在存储之后,在rdtsc存储缓冲区耗尽时将记录的之前。)

或使用序列化指令,如cpuid. 英特尔在其手册中使用的技术术语是“序列化指令”,即在先前的指令退役之前无法启动的指令,并在后续指令发出之前耗尽存储缓冲区。这就是我认为你所说的“线性化”。 MFENCE/SFENCE/etc“序列化内存而不是指令执行”?

x86 CPU 有多少条内存屏障指令?列出 x86 的序列化指令。


或者,如果您将“调用完成”定义为“提交到 L1d”,那么线性化与 x86 和几乎所有 ISA 上的 SC 相同:一旦将存储提交到 L1d 缓存,它对所有内核都是全局可见的。从定义上讲,在发生这种情况之前,核心几乎不会跟踪自己的商店。

我们运行线程的所有 CPU 都有缓存一致的共享内存,因此不需要显式刷新来确保可见性,并且保持一致 L1d = 全局可见。MESI 一致性要求高速缓存行在修改之前由内核独占。


推荐阅读