首页 文章

x86 CPU是否重新排序指令?

提问于
浏览
4

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

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

但x86 CPU是否重新排序指令?如果没有,那么就没有必要使用内存屏障了吧?

1 回答

  • 10

    重新排序

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

    记忆障碍

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

    现在肯定的是,无序执行是无序内存访问功能的主要驱动因素,或者也许是对驱动日益强大的现代CPU无序功能的追求.2905405_ . 事实上,两者都可能同时成为现实:增加无序功能可以从强大的内存重新排序功能中获益,同时如果没有良好的无序功能,也无法进行积极的内存重新排序和重叠,因此他们互相帮助的是一种自我强化的总和 - 超过部分的循环 .

    所以,是的,无序执行和内存重新排序肯定有关系;但是, you can easily get re-ordering without out-of-order execution !例如,核心本地存储缓冲区通常会导致明显的重新排序:在执行点,存储在一致性点处不可见,这会延迟本地存储相对于需要在其点读取其值的本地负载 . 执行 .

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

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

    x86重新订购

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

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

    • 商店具有单一的全局可见性顺序,由所有CPU一致地观察,但下面将放松一条此规则 .

    • 本地加载操作永远不会针对其他本地加载操作重新排序 .

    • 本地存储操作从不针对其他本地存储操作重新排序(即,在指令流中较早出现的存储总是在全局顺序中较早出现) .

    • 可以针对较早的本地存储操作重新排序本地加载操作,使得加载看起来更早地执行wrt全局商店订单比本地商店,但反向(早期加载,旧商店)不是真的 .

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

    好吧,x86做了大量的额外工作来准确跟踪加载和存储的原始顺序,并确保不会出现违反规则的内存重新排序 . 例如,假设load 2在加载1之前执行(加载1在程序顺序中较早出现),但是在加载1和加载2执行期间,两个涉及的缓存行都处于"exclusively owned"状态:已经重新排序,但是本地核心知道无法观察到它,因为没有其他人能够窥探这种本地操作 .

    与上述优化一致,CPU也使用推测执行:不按顺序执行所有操作,即使有可能在稍后某个核心可以观察到差异,但实际上不执行指令直到这样的观察不可能 . 如果确实发生了这样的观察,则将CPU回滚到较早的状态并再次尝试 . 这是英特尔"memory ordering machine clear"的原因 .

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

    所有这一切的结果是 yes ,x86需要内存屏障来专门防止所谓的StoreLoad重新排序(对于需要此保证的算法) . 在x86中你没有发现很多独立的内存障碍,因为大多数并发算法也需要原子操作,例如原子添加,测试和设置或比较和交换,而在x86上,这些都有完全的障碍 . 自由 . 因此,使用显式内存屏障指令(如 mfence )仅限于您不进行原子读取 - 修改 - 写入操作的情况 .

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


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

相关问题