乱序指令执行:是否保留了提交顺序?

Out-of-order instruction execution: is commit order preserved?

一方面,维基百科写了乱序执行的步骤:

  1. Instruction fetch.
  2. Instruction dispatch to an instruction queue (also called instruction buffer or reservation stations).
  3. The instruction waits in the queue until its input operands are available. The instruction is then allowed to leave the queue before earlier, older instructions.
  4. The instruction is issued to the appropriate functional unit and executed by that unit.
  5. The results are queued.
  6. Only after all older instructions have their results written back to the register file, then this result is written back to the register file. This is called the graduation or retire stage.

类似的资料可以在"Computer Organization and Design"书中找到:

To make programs behave as if they were running on a simple in-order pipeline, the instruction fetch and decode unit is required to issue instructions in order, which allows dependences to be tracked, and the commit unit is required to write results to registers and memory in program fetch order. This conservative mode is called in-order commit... Today, all dynamically scheduled pipelines use in-order commit.

因此,据我了解,即使指令执行是以乱序方式完成的,它们的执行结果也会保存在重新排序缓冲区中,然后提交给 memory/registers以确定的顺序。

另一方面,众所周知,现代 CPU 可以为性能加速目的重新排序内存操作(例如,可以重新排序两个相邻的独立加载指令)。维基百科对此进行了描述 here

您能否解释一下这种差异?

TL:DR:内存排序与乱序执行不同。它甚至发生在有序流水线 CPUs.

顺序提交是必要的1 精确的异常可以回滚到出错的指令,没有任何指令已经退出。乱序执行的基本规则是不要破坏单线程代码。如果您在没有任何其他机制的情况下允许乱序提交(退出),则可能会发生页面错误,而某些较晚的指令已经执行一次,and/or 某些较早的指令尚未执行。这将使在处理页面错误后无法以正常方式重新启动执行。

(按顺序 issue/rename 和依赖跟踪负责在没有异常的正常情况下正确执行。)

内存排序是关于 其他 内核所看到的。另请注意,您引用的只是在谈论将结果提交到寄存器文件,而不是内存。

(脚注 1Kilo-instruction Processors: Overcoming the Memory Wall 是一篇关于检查点状态的理论论文,允许在异常发生前的某个时刻回滚到一致的机器状态,从而允许更大的输出-of-order windows 没有那种大小的巨大 ROB。AFAIK,没有主流商业设计使用过它,但它表明理论上除了严格有序的退役之外还有其他方法来构建可用的 CPU.

据报道,Apple 的 M1 的乱序 window 比同时代的 x86 产品大得多,但我没有看到任何明确的信息表明它使用了除了非常大的 ROB 以外的任何东西。)


由于每个核心的私有 L1 缓存与系统中的所有其他数据缓存一致,内存排序是指令何时读取或写入的问题 缓存 。这与他们从无序核心退出时是分开的。

加载从缓存中读取数据时变得全局可见。这或多或少是在他们“执行”时发生的,而且肯定是在他们退休(又名提交)之前。

当数据提交到缓存时,商店将变得全局可见。这必须等到它们被认为是非推测的,即没有异常或中断会导致必须“撤消”存储的回滚。因此,存储可以在它从无序核心退出时尽早提交到 L1 缓存。

但即使是有序 CPUs 使用存储队列或 to hide the latency of stores that miss in L1 cache. The out-of-order machinery doesn't need to keep tracking a store once it's known that it will definitely happen, so a store insn/uop can retire even before it commits to L1 cache. The store buffer holds onto it until L1 cache is ready to accept it. i.e. when it owns the cache line (Exclusive or Modified state of the MESI cache coherency protocol),并且内存排序规则现在允许存储变得全局可见。

另请参阅我在

上的回答

据我了解,商店的数据在无序核心中“执行”时会添加到商店队列中,这就是商店执行单元所做的事情。 (Store-address 写入地址,store-data 在 allocation/rename 时间将数据写入为其保留的存储缓冲区条目,因此这些部分中的任何一个都可以在 CPUs 上首先执行,其中这些部分单独安排,例如英特尔。)

加载必须探测存储队列,以便它们看到最近存储的数据。


对于像 x86 这样具有强排序的 ISA,存储队列必须保留 ISA 的内存排序语义。即商店不能与其他商店重新订购,并且商店不能在之前更早加载之前变得全局可见。 (LoadStore reordering isn't allowed (nor is StoreStore or LoadLoad), only StoreLoad reordering).

David Kanter 的 article on how TSX (transactional memory) could be implemented in different ways than what Haswell does 提供了对内存排序缓冲区的一些见解,以及它是如何与跟踪 instruction/uop 重新排序的重新排序缓冲区 (ROB) 分开的结构。他首先描述了当前的工作方式,然后介绍了如何对其进行修改以跟踪可以作为一个组提交或中止的事务。