gpt4 book ai didi

multithreading - x86 CPU 会重新排序指令吗?

转载 作者:行者123 更新时间:2023-12-04 13:18:28 25 4
gpt4 key购买 nike

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

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

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

最佳答案

重新排序

是的,来自 Intel 和 AMD 的所有现代 x86 芯片都在一个窗口中积极地重新排序指令,这两个制造商的最新 CPU 上大约有 200 条指令(即,一条新指令可能会执行,而“过去”超过 200 条指令的旧指令仍然是等待)。这对于单个线程来说通常都是不可见的,因为 CPU 仍然通过尊重依赖性来保持当前线程串行执行的错觉,所以从当前执行线程的角度来看,就好像指令是串行执行的。

内存障碍

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

现在,乱序执行确实是乱序内存访问能力的主要驱动因素,或者它可能是对 MLP (Memory Level Parallelism) 的追求。这插入了现代 CPU 越来越强大的乱序功能。事实上,两者都可能同时成立:增加无序能力从强大的内存重新排序能力中受益很多,同时如果没有良好的无序能力,积极的内存重新排序和重叠是不可能的,所以它们以一种自我强化的总和大于部分的循环方式互相帮助。

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

正如彼得还在 comment thread 中指出的那样当在有序设计中允许负载重叠时,您还可以获得一种加载-加载重新排序:加载 1 可能会启动,但在没有使用其结果的指令的情况下,流水线有序设计可能会继续执行以下指令这可能包括另一个加载 2。如果加载 2 是缓存命中而加载 1 是缓存未命中,则加载 2 可能在加载 1 的时间更早得到满足,因此可能会交换表观顺序重新排序。

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

x86 重新排序

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

同时,x86定义了相当严格的内存模型,禁止了大多数可能的重排序,大致总结如下:

  • Stores 有一个单一的全局可见性顺序,所有 CPU 都一致地观察到,受以下此规则的松动影响。
  • 本地加载操作永远不会相对于其他本地加载操作重新排序。
  • 本地存储操作永远不会相对于其他本地存储操作重新排序(即,在指令流中较早出现的存储总是在全局顺序中较早出现)。
  • 本地加载操作可以相对于较早的本地存储操作重新排序,使得加载似乎比本地存储更早执行全局存储顺序,但反过来(更早的加载,较旧的存储)则不成立。

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

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

    与上述优化相一致,CPU 还使用推测执行:乱序执行所有内容,即使在稍后某个时候某个核心可能会观察到差异,但在这种观察不可能之前,不要实际提交指令.如果确实发生了这样的观察,则将 CPU 回滚到较早的状态并重试。这就是 Intel 上“内存排序机清除”的原因。

    因此,可以定义一个根本不允许任何重新排序的 ISA,但在幕后进行重新排序,但仔细检查是否未观察到它。 PA-RISC 就是这种顺序一致架构的一个例子。 Intel 有一个强大的内存模型,它允许一种类型的重新排序,但不允许许多其他类型的重新排序,但是每个芯片内部可能会做更多(或更少)的重新排序,只要它们能保证在可观察的意义上遵守规则(在这个从某种意义上说,它与编译器在优化方面所遵循的“as-if”规则有些相关)。

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

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

    1 当然,如果你足够努力,这种重新排序是可见的!最近的一个高影响示例是 Spectre 和 Meltdown 漏洞利用,它们利用推测性乱序执行和缓存侧 channel 来违反内存保护安全边界。

    关于multithreading - x86 CPU 会重新排序指令吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50307693/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com