6

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

  1. 取指令。
  2. 将指令分派到指令队列(也称为指令缓冲区或保留站)。
  3. 指令在队列中等待,直到其输入操作数可用。然后允许该指令在较早的较旧指令之前离开队列。
  4. 该指令被发布到适当的功能单元并由该单元执行。
  5. 结果排队。
  6. 只有在所有旧指令的结果都写回寄存器文件之后,才会将该结果写回寄存器文件。这称为毕业或退休阶段。

类似的信息可以在《计算机组织与设计》一书中找到:

为了使程序表现得好像它们在一个简单的有序流水线上运行,指令获取和解码单元需要按顺序发出指令,这允许跟踪依赖关系,并且提交单元需要将结果写入寄存器和程序获取顺序中的内存。这种保守的模式被称为按序提交……今天,所有动态调度的管道都使用按序提交。

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

另一方面,众所周知,现代 CPU 可以重新排序内存操作以提高性能(例如,可以重新排序两个相邻的独立加载指令)。维基百科在这里写到。

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

4

1 回答 1

8

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

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

(按顺序发出/重命名和依赖跟踪负责在正常情况下正确执行,没有例外。)

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

脚注 1Kilo-instruction Processors: Overcoming the Memory Wall是一篇关于检查点状态的理论论文,以允许在异常前的某个时间点回滚到一致的机器状态,允许更大的无序窗口而没有巨大的 ROB AFAIK,没有主流商业设计使用过它,但它表明除了严格按顺序退休之外,理论上还有其他方法来构建可用的 CPU。

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


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

当负载从缓存中读取数据时,它们变得全局可见。这或多或少在他们“执行”时,而且绝对是在他们退休(又名提交)之前。

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

但即使是有序 CPU 也使用存储队列或存储缓冲区来隐藏 L1 缓存中未命中的存储的延迟。一旦知道它肯定会发生,乱序机器就不需要继续跟踪存储,因此存储 insn/uop 甚至可以在提交到 L1 缓存之前退出。存储缓冲区保留它,直到 L1 缓存准备好接受它。即,当它拥有缓存行(MESI 缓存一致性协议的独占或修改状态)时,内存排序规则允许存储现在全局可见。

另请参阅我对写入缓存策略的写入分配/获取的回答

据我了解,当商店的数据在乱序核心中“执行”时,它会被添加到商店队列中,这就是商店执行单元所做的事情。(存储地址写入地址,存储数据将数据写入分配/重命名时为其保留的存储缓冲区条目,因此这些部分中的任何一个都可以首先在这些部分单独调度的 CPU 上执行,例如 Intel。 )

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


对于像 x86 这样具有强排序的 ISA,存储队列必须保留 ISA 的内存排序语义。即商店不能与其他商店重新排序,并且商店不能在更早的加载之前成为全局可见的。不允许 LoadStore 重新排序(StoreStore 或 LoadLoad 也不允许),仅允许 StoreLoad 重新排序)。

David Kanter关于如何以不同于 Haswell 所做的方式实现 TSX(事务性内存)的文章提供了对内存顺序缓冲区的一些见解,以及它是如何与跟踪指令/微指令重新排序的重新排序缓冲区 (ROB) 分开的结构。他首先描述了当前的工作方式,然后介绍了如何对其进行修改以跟踪可以作为一个组提交或中止的事务。

于 2016-09-23T22:22:25.480 回答