乱序执行和记忆围栏

时间:2011-09-08 10:52:36

标签: c x86 cpu memory-barriers memory-fences

我知道现代CPU可以无序执行,但是它们总是按顺序退出结果,如维基百科所述。

“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

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

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

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

3 个答案:

答案 0 :(得分: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节

答案 1 :(得分:7)

内存栅栏确保所有其他内核都可以看到围栅内变量的所有更改,以便所有内核都具有最新的数据视图。

如果你没有放置内存,那么内核可能正在使用错误的数据,特别是在场景中可以看到这种情况,其中多个内核将处理相同的数据集。在这种情况下,您可以确保在CPU 0执行某些操作时,对数据集所做的所有更改现在对所有其他核心可见,然后可以使用最新信息。

  

一些架构,包括无处不在的x86 / x64,提供了几种架构   记忆障碍指令,包括有时称为的指令   “全围栏”。完整的栅栏确保所有加载和存储操作   在任何负荷之前,围栏之前都会进行   在围栏后发布的商店。

如果核心开始使用数据集上过时的数据,它怎么能得到正确的结果呢?如果最终结果是否以正确的顺序完成,则无论是否最终结果都可以。

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

  

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

     

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

这意味着将事物写入此缓冲区,然后在某些时候将缓冲区写入缓存。因此,缓存可以包含不是最新的数据视图,因此通过缓存一致性的另一个CPU也将不具有最新数据。存储缓冲区刷新是最新数据可见的必要条件,我认为这实际上是内存栅栏将在硬件级别发生的事情。

编辑:

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

  

可以在处理器#2分配给f之前插入内存屏障   确保x的新值对于其他处理器可见   在f。值的变化之前。

答案 2 :(得分:2)

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

  

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

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

每个核心的行为就好像它自己的内存访问在退役时发生,但其他核心可能会在不同时间看到这些访问。

(在x86和ARM上,我认为只有商店可以观察到这一点,但是例如,Alpha可能会从内存中加载一个旧值.x86 SSE2的指令含有比正常x86行为更弱的guarentees。)

PS。从记忆中,废弃的Sparc ROCK实际上可以无序退出,它花费了功率和晶体管来确定何时这是无害的。它因功耗和晶体管数量而被抛弃......我不相信任何通用CPU已经被无序退役带入市场。