11

我知道现代 CPU 可以乱序执行,但是它们总是按顺序退出结果,如 wikipedia 所述。

“Out of Oder 处理器及时用其他准备好的指令填充这些“插槽”,然后在最后对结果重新排序,以使指令看起来像正常处理

现在据说在使用多核平台时需要内存栅栏,因为由于乱序执行,可以在此处打印错误的x值。

Processor #1:
 while f == 0
  ;
 print x; // x might not be 42 here

Processor #2:
 x = 42;
 // Memory fence required here
 f = 1

现在我的问题是,由于乱序处理器(我假设多核处理器的核心)总是按顺序退出结果,那么内存栅栏的必要性是什么。多核处理器的内核是否只看到从其他内核退出的结果,或者它们也看到正在进行的结果?

我的意思是在我上面给出的示例中,当处理器 2 最终将退出结果时,x的结果应该在f之前,对吗?我知道在乱序执行期间,它可能在x之前修改了f ,但它一定没有在x之前退休,对吧?

现在有了按顺序退出结果和缓存一致性机制,为什么在 x86 中还需要内存栅栏?

4

3 回答 3

15

本教程解释了这些问题: http: //www.hpl.hp.com/techreports/Compaq-DEC/WRL-95-7.pdf

FWIW,在现代 x86 处理器上发生内存排序问题,原因是虽然 x86 内存一致性模型提供了相当强的一致性,但需要明确的屏障来处理读写后的一致性。这是由于所谓的“存储缓冲区”。

也就是说,x86 是顺序一致的(很好且易于推理),只是加载可能会在较早的存储中重新排序。也就是说,如果处理器执行序列

store x
load y

然后在处理器总线上,这可能被视为

load y
store x

这种行为的原因是前面提到的存储缓冲区,它是写在系统总线上之前的一个小缓冲区。OTOH,加载延迟是性能的关键问题,因此允许加载“跳队列”。

请参阅http://download.intel.com/design/processor/manuals/253668.pdf中的第 8.2 节

于 2011-09-08T11:00:42.943 回答
8

内存栅栏确保在栅栏之前对变量的所有更改对所有其他内核都是可见的,因此所有内核都拥有最新的数据视图。

如果您不设置内存栅栏,则内核可能正在处理错误的数据,尤其是在多个内核将在同一数据集上工作的场景中。在这种情况下,您可以确保当 CPU 0 执行某些操作时,对数据集所做的所有更改现在对所有其他内核可见,然后它们可以使用最新信息。

一些架构,包括无处不在的 x86/x64,提供了一些内存屏障指令,包括有时称为“全栅栏”的指令。完整的栅栏确保栅栏之前的所有加载和存储操作都将在栅栏之后发出的任何加载和存储之前提交。

如果一个核心开始处理数据集上的过时数据,它怎么能得到正确的结果?不管最终结果是否要呈现为好像所有事情都按正确的顺序完成一样。

密钥在存储缓冲区中,它位于缓存和 CPU 之间,并执行以下操作:

存储缓冲区对远程 CPU 不可见

存储缓冲区允许保存对内存和/或缓存的写入以优化互连访问

这意味着内容将被写入此缓冲区,然后在某个时候将缓冲区写入缓存。因此缓存可能包含不是最新数据的视图,因此另一个 CPU 通过缓存一致性也不会拥有最新数据。存储缓冲区刷新对于最新数据可见是必要的,我认为这基本上是内存栅栏将导致在硬件级别发生的情况。

编辑:

对于您用作示例的代码,维基百科是这样说的:

可以在处理器#2 分配给 f 之前插入内存屏障,以确保 x 的新值在 f 值更改时或之前对其他处理器可见。

于 2011-09-08T11:01:25.423 回答
2

只是为了明确说明先前答案中隐含的内容,这是正确的,但与内存访问不同:

CPU 可以乱序执行,但是它们总是按顺序退出结果

指令的引退与执行存储器访问是分开的,存储器访问可以在与指令引退不同的时间完成。

每个核心都会像在退休时发生自己的内存访问一样,但其他核心可能会在不同的时间看到这些访问。

(在 x86 和 ARM 上,我认为只有商店明显受制于此,但例如,Alpha 可能会从内存中加载旧值。x86 SSE2 的指令具有比正常 x86 行为更弱的保证)。

PS。根据记忆,废弃的 Sparc ROCK 实际上可能会无序退役,它消耗了电力和晶体管来确定何时这是无害的。由于功耗和晶体管数量,它被放弃了......我不相信任何通用 CPU 已经在无序退役的情况下被购买到市场。

于 2017-12-27T08:32:42.847 回答