x86 CPU是否重新排序指令?

时间:2018-05-12 15:12:35

标签: multithreading assembly x86 cpu-architecture memory-barriers

我已经读过一些CPU重新排序指令,但这对于单线程程序来说不是问题(指令仍会在单线程程序中重新排序,但看起来好像指令是按顺序执行的),它是只是多线程程序的问题。

要解决指令重新排序的问题,我们可以在代码中的适当位置插入内存屏障。

但x86 CPU是否重新排序说明?如果没有,那么就没有必要使用内存屏障,对吗?

1 个答案:

答案 0 :(得分:14)

重新排序

是的,来自英特尔和AMD的所有现代x86芯片都在一个窗口上积极地重新排序指令,该窗口在两个制造商的最新CPU上大约200个指令深度(即,一条新指令可以执行而旧指令超过200条指令"在过去"仍在等待)。这通常对于单个线程都是不可见的,因为CPU仍然通过遵循依赖关系保持当前线程的串行执行错误 1 ,因此从当前执行线程的角度来看,它是 - 如果说明是连续执行的。

内存障碍

那应该回答这个名义上的问题,但接下来你的第二个问题就是记忆障碍。但是,它包含一个错误的假设,即指令重新排序必然会导致(并且是唯一的原因)可见内存重新排序。实际上,指令重新排序对于跨线程内存重新排序来说既不充分也不必要。

现在,无序执行是无序内存访问功能的主要驱动程序,或者可能是驱动MLP (Memory Level Parallelism)的任务现代CPU的日益强大的无序功能。事实上,两者都可能同时成为现实:增加无序功能可以从强大的内存重新排序功能中获益,同时在没有良好的无序功能的情况下,不可能进行积极的内存重新排序和重叠因此,他们互相帮助,形成一种自我强化的总和 - 超过部分的循环。

所以是的,无序执行和内存重新排序肯定有关系;但是,您可以在不执行无序执行的情况下轻松获得重新订购!例如,核心本地存储缓冲区通常会导致明显的重新排序:在执行时,存储不会直接写入缓存(因此在一致性点处不可见),这会延迟本地存储关于需要在执行时读取其值的本地负载。

正如Peter在comment thread中指出的那样,当在有序设计中允许加载重叠时,您也可以获得一种负载加载重新排序:加载1可以启动但是在没有消耗其结果的指令的情况下,流水线有序设计可以继续执行以下指令,其可能包括另一个负载2.如果负载2是高速缓存命中并且负载1是高速缓存未命中,则可以更早地满足负载2从负载1开始的时间,因此可以交换明显的顺序重新排序。

因此我们看到所有跨线程内存重新排序不是由指令重新排序引起的,但某些指令重新排序意味着不在命令内存访问,对吗?没那么快!这里有两种不同的上下文:在硬件级别发生的事情(即,内存访问指令是否可以,实际上是无序执行),以及ISA和平台文档所保证的内容(通常称为< em>内存模型适用于硬件)。

x86重新订购

例如,在x86的情况下,现代芯片可以相对于彼此自由地重新排序任何加载和存储流:如果加载或存储准备好执行,CPU通常会尝试它尽管存在早期未完成的加载和存储操作。

与此同时,x86定义了一个非常严格的内存模型,禁止大多数可能的重新排序,大致总结如下:

  • 商店拥有单一的全球可见性顺序,所有CPU都会一致地观察,但下面会放松一条规则。
  • 本地加载操作永远不会针对其他本地加载操作重新排序。
  • 本地存储操作从不对其他本地存储操作进行重新排序(即,指令流中较早出现的存储始终在全局顺序中出现)。
  • 本地加载操作可以针对早期本地存储操作重新排序,这样加载似乎比全局存储顺序早于本地存储执行,但反过来(早期加载,更早商店)不是真的。

所以实际上大多数内存重新排序都不允许相对于每个外部加载,相对于彼此存储,以及相对于后期存储的加载。然而我在上面说过x86几乎可以自由地执行无序的所有内存访问指令 - 你如何协调这两个事实呢?

嗯,x86做了大量的额外工作来准确跟踪加载和存储的原始顺序,并确保不会看到违反规则的内存重新排序。例如,让我们说加载2在加载1之前执行(加载1在程序顺序中出现得更早),但是两个涉及的缓存行都在&#34;独占拥有的#34;在负载1和负载2执行期间的状态:已经重新排序,但本地核心知道无法被观察,因为没有其他人能够查看此本地操作。

与上述优化一致,CPU也使用推测执行:不按顺序执行所有操作,即使有可能在稍后某个核心可以观察到差异,但实际上并不是提交指示,直到无法进行此类观察。如果确实发生了这样的观察,则将CPU回滚到较早的状态并再次尝试。这是&#34;内存订购机清除&#34;在英特尔。

因此可以定义一个根本不允许任何重新排序的ISA,但是在封面下会重新排序,但要仔细检查它是不是观测到的。 PA-RISC是这种顺序一致的架构的一个例子。英特尔有一个强大的内存模型,允许一种类型的重新排序,但不允许其他许多,但每个芯片内部可以做更多(或更少)重新排序,只要他们可以保证在可观察的意义上遵守规则(在此感觉,它有点与编译器在优化时所遵循的&#34; as-if&#34;规则有关。)

所有这一切的结果是,x86需要内存屏障来防止所谓的StoreLoad重新排序(对于需要此保证的算法)。您在x86中实际上找不到许多独立的内存障碍,因为大多数并发算法还需要原子操作,例如原子添加,测试和设置或比较和交换,以及在x86上,所有这些都免费提供完全障碍。因此,mfence等显式内存屏障指令的使用仅限于您不进行原子读 - 修改 - 写操作的情况。

Jeff Preshing的Memory Reordering Caught in the Act 有一个例子确实在真正的x86 CPU上显示了内存重新排序,mfence阻止了它。

1 当然,如果你努力尝试,这样的重新排序是可见的!最近的一个影响很大的例子是Spectre和Meltdown漏洞,它利用推测性无序执行和缓存侧通道来破坏内存保护安全边界。