【问题标题】:Out of Order Execution and Memory Fences乱序执行和内存栅栏
【发布时间】:2011-11-12 21:32:42
【问题描述】:

我知道现代 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 中的内存栅栏?

【问题讨论】:

  • 请注意,内存栅栏总是成对出现在正确的代码中:当两个线程通信时,每个线程必须执行一些内存访问顺序(=栅栏)。通常,这些栅栏之一具有释放语义,另一个具有获取语义。在您的伪代码中,处理器#2 应该在分配之间执行写栅栏(释放语义),处理器#1 应该在循环和print 之间添加一个读栅栏(获取语义)。在特定平台上可能不需要某些栅栏,但任何源代码都应包含这两个栅栏(可能编译为 noops)。

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


【解决方案1】:

本教程解释了这些问题: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 节

【讨论】:

  • Janneb,您能否稍微解释一下存储缓冲区以及为什么它们在这种情况下很重要?
  • 缓存一致性不能确保 x86 中的 read-after-write 一致性吗?
  • @MetallicPriest:啊,不过,我怀疑在您的具体示例中实际上不需要障碍。我编辑了帖子以反映这一点,并添加了对 x86 内存模型中允许重新排序的说明。
  • @janneb,他以维基百科关于内存屏障的文章为例。
  • FWIW 和 OTOH 减一。
【解决方案2】:

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

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

一些架构,包括无处不在的 x86/x64,提供了几个 内存屏障指令,包括有时称为的指令 “全围栏”。完整的围栏确保所有加载和存储操作 在围栏之前将在任何负载之前提交,并且 沿着栅栏发行的商店。

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

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

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

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

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

编辑:

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

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

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-29
    • 2016-08-27
    • 2016-08-17
    • 2015-11-03
    • 2020-03-09
    • 2020-05-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多