乱序执行是否会导致投机式内存访问?

5
当乱序处理器遇到如下情况时:
LOAD R1, 0x1337
LOAD R2, $R1
LOAD R3, 0x42

假设所有访问都会导致缓存未命中,处理器是否可以在请求$R1或者0x1337的内容之前就向内存控制器请求0x42的内容呢?如果是这样的话,假定访问$R1会引发异常(例如分段错误),我们可以认为0x42被进行了推测加载,对吗?
顺便问一下,当加载存储单元向内存控制器发送请求时,在收到上一个请求的答复之前,它是否可以发送第二个请求?
我的问题并没有针对任何特定的架构。欢迎回答任何主流架构相关的问题。
4个回答

6
你的问题的答案取决于你的CPU的内存排序模型,这与CPU允许乱序执行不同。如果CPU实现了Total store ordering(例如x86或Sparc),那么你的问题的答案是0x42将不会在0x1337之前加载。
如果CPU实现了轻松的内存模型(例如IA-64、PowerPC、alpha),那么在没有内存栅栏指令的情况下,哪个先访问都不确定。除非你正在进行IO,或者处理多线程代码,否则这应该并不重要。
你应该注意,一些CPU(例如Itanium)具有放松的内存模型(因此读取可能是无序的),但没有任何乱序执行逻辑,因为它们期望编译器以最优方式对指令和推测性指令排序,而不是花费硅空间来处理OOE。

在今天的NUMA x86系统中,这仍然是正确的吗?我无法想到一种特别有效的方法来强制执行跨不同内存控制器的写入顺序。 - tc.
4
我可以向您保证,英特尔x86处理器会执行推测性内存访问。自P6大约1996年以来就是这样。但是,它们只会对操作系统指示为常规内存(WB)而不是具有副作用的未缓存内存(UC,使用MTRRs标记)的内存位置执行此类推测性内存访问。执行此类推测的处理器通常具有逻辑以检测违反内存排序模型的情况。也就是说,英特尔x86确实进行了推测性加载,但是检测到违规情况,因此大多数程序员不会注意到。但是,一些低级程序员可能会注意到。 - Krazy Glew
1
今天看起来这个“特性”可以被利用,由于英特尔 P6 和更新处理器中虚拟内存的实现方式:https://news.ycombinator.com/item?id=16046636 - RedShift
1
@camelccc:Krazy Glew正在谈论为什么要清空由其他逻辑处理器引起的内存顺序违规的流水线?,不仅仅是硬件预取。CPU确实会重新排序负载执行,但然后检查最终结果是否符合ISA上纸面内存模型保证的法律要求(即,先前加载的缓存行仍然有效,因此仍然包含我们现在允许加载的数据)。如果不符合要求,则清除依赖于此可能不安全的推测的正在进行中的指令,并回滚到已知的安全状态。 - Peter Cordes
1
因此,您可以获得放松的加载顺序(大多数情况下),同时仍然遵守内存模型规则,其中每个加载实际上都是获取加载。 - Peter Cordes
显示剩余2条评论

4
这似乎是超标量CPU和多个load-store单元的一个合理结论。现在,多通道内存控制器非常常见。
对于乱序指令执行来说,需要耗费大量逻辑来确定指令是否依赖于其他流中的指令 - 不仅是寄存器依赖关系,还包括对内存的操作。处理异常也需要耗费大量逻辑:CPU需要完成流中的所有指令直到出现故障(或者将其部分转移到操作系统)。
对于大多数应用程序看到的编程模型来说,这种效果从未显现出来。从内存的角度来看,载入不总是按照预期顺序进行 - 但是当使用缓存时,情况总是如此。
显然,在需要考虑负载和存储顺序的情况下 - 例如访问设备寄存器时,必须禁用OOE。POWER架构具有出色的EIEIO指令实现此目的。
ARM Cortex-A系列的一些成员提供OOE - 我怀疑由于这些设备的功率限制以及缺少强制排序指令,载入-存储总是按顺序完成。

Marko,谢谢你的回答。正如你所说,我对OOE / 超标量CPU提出了一个逻辑假设,但我真的想知道它是否真的是真的 :) - João Fernandes
ARM(v6?)架构参考手册提到了一种称为DataMemoryBarrier的东西(也许是伪指令或宏?),实际上是对CP15的写操作。我不确定它是否特权(它旁边有像禁用缓存之类的东西应该是特权级别的),但它确实存在。 - tc.
dmb(伪)指令是内存障碍(也称为内存屏障)。它是ARMv7中提供的唯一这样的指令(其他体系结构提供了更具体的加载和存储栅栏)。这确实强制完成排序,但也可以防止硬件(或在另一个核上运行的线程)依赖于存储效果变得可见的其他危险。这是一个非特权指令,在用户空间代码中有几种情况需要它-例如实现原子操作。 - marko
ARM还提供了ISBDSB,您可以查看ARM ARM以了解它们的用例及其对管道、系统总线的影响。 - sgupta

2
与x86相关的问题:为什么会因其他逻辑处理器引起的内存顺序违规而刷新流水线?。可观测结果将遵守x86排序规则,但是在微架构上,它确实可能提前加载。(当然,这是来自缓存;硬件预取是不同的)。
如果地址对于一个加载操作不准备好,或者如果它在高速缓存中未命中,则OoO执行的CPU确实会重新排列加载执行。但是在x86上,为了与强内存模型(程序顺序+具有存储转发的存储缓冲区)保持正确性,核心会检查最终结果是否符合ISA的纸面内存模型保证的法律要求。(即先前从中加载的缓存行仍然有效,因此仍然包含我们现在可以加载的数据)。如果不是,则取消依赖于可能不安全的推测的正在进行的指令并回滚到已知的安全状态。
因此,现代x86获得了松散加载排序的性能(大多数情况下),同时仍然保持每个加载实际上是获取加载的内存模型规则。但是,如果您做了流水线不喜欢的事情,例如虚假共享(这已经够糟糕了),则需要进行管道清除。
具有强内存模型(Sparc TSO)的其他CPU可能不会这么积极。弱内存模型允许后续加载提前完成。
当然,这是从缓存中读取的;仅在高速缓存未命中时,要求加载请求才被/内存控制器看到。但是HW预取器可以异步访问CPU的内存;这就是它们在CPU运行加载数据的指令之前将数据放入缓存的方式,理想情况下完全避免缓存未命中的方法。
是的,内存子系统是流水线化的,例如Skylake每个核心有12到16个未完成请求。(12个LFB用于L1<->L2,我记得L2中有16个超级队列条目。)

相关:https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ 描述了CPU可能会重新排序对一致共享缓存的访问的某些机制。 另请参阅How is load->store reordering possible with in-order commit?以了解其中更令人惊讶的一个,以及How does Intel X86 implements total order over stores - Peter Cordes

1
一款合规的SPARC处理器 必须 实现TSO,但也 可以 实现RMO和PSO。除非你知道你的特定硬件平台未实现RMO和PSO,否则需要知道您的操作系统运行在哪种模式下。

谢谢Spark Ler。我已经知道这个事实,但为了完整起见,在这里有它也是好的。 - João Fernandes
1
真的,但所有版本的Solaris都只在TSO中运行。 Ultrasparc III及更高版本仅实现TSO,因此我相信Linux多年前就放弃了RMO和PSO支持。 如果有人知道支持RMO的Sparc配置,请告诉我。 - camelccc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接