在流水线CPU上如何确保一条指令在第二条指令开始之前完成?

wan*_*907 -4 cpu assembly cpu-architecture pipelining

假设有两条顺序指令,如下所示:

instruction A
instruction B
Run Code Online (Sandbox Code Playgroud)

由于 CPU 流水线的原因,B 将在 A 完成之前启动。

是否存在一种机制来确保 B 在 A 完成后启动?

更新:
很抱歉我没有准确描述问题。我的意思是,这两条指令具有应用程序级排序依赖性,但没有危险。例如,在事务系统中,第一条指令将日志刷新到持久存储,第二条指令通知客户端有关事务提交的信息。因此,在第一条指令完成之前,我们无法执行第二条指令。如何提供这个执行指令?

Cod*_*ray 5

由于 CPU 流水线的原因,B 将在 A 完成之前启动。

所以?为什么这是个问题?

在基本的流水线架构中,指令A将在第一个周期开始执行,然后指令B将在下一个周期开始执行。

基本的 5 级 RISC 流水线为例,它看起来像这样:

Clock Cycle   |    1    |    2     |     3     |      4      |      5      |      6      |
--------------|---------------------------------------------------------------------------
Instruction A |  Fetch  |  Decode  |  Execute  | Mem. Access |  Writeback  |
Instruction B |         |  Fetch   |  Decode   |   Execute   | Mem. Access |  Writeback  |
Run Code Online (Sandbox Code Playgroud)

处理器将在第一个时钟周期开始获取指令 A。在第二个时钟周期,它将开始解码指令 A,同时读取指令B。依此类推,沿着流水线进行。

其工作如此良好的原因是指令获取单元是与指令解码单元完全独立的硬件(即使两者可以在同一块硅片上实现),因此保持这些单元中的每一个都被占用是有意义的同时地。这是实现指令级并行性(ILP)的一种机制。

最终,您可以看到指令 A 将在周期 5 完成,而指令 B 直到周期 6 才能完成。不过,这比指令 A 在周期 5 完成而指令 B 直到周期6才能开始要好得多,从而推迟了直到第 11 周期完成。

处理器内部的逻辑处理指令依赖性,因此如果指令 B 以某种方式依赖于指令 A 的结果,处理器的解码器将能够检测到这一点,并将停止指令B 的执行,直到其数据可用(即,直到指令 A在管道中走得足够远,其结果已准备就绪)。这一切都为您无缝处理,但它确实引入了性能成本(管道气泡),因此您希望尽可能避免它。这意味着编写代码时,使具有依赖关系的指令尽可能地彼此分散,并在其间散布独立的指令。

是否存在一种机制来确保 B 在 A 完成后启动?

是的,这种机制通常存在,但您通常不想使用它们,因为它们会破坏管道的全部优势,从而减慢执行速度。

这些机制被称为序列化指令(或有时称为“屏障”),因为它们建立了一个屏障,导致执行在特定点被序列化。

例如,在 x86 架构上,该CPUID指令是一条序列化指令(实际上是多个指令之一)。所以你可以这样做:

Instruction A
CPUID
Instruction B
Run Code Online (Sandbox Code Playgroud)

这将确保指令 B 在指令 A 执行完成之前不会启动。

来自英特尔架构手册:

CPUID可以在任何特权级别执行以串行化指令执行。串行化指令执行可保证对先前指令的标志、寄存器和存储器的任何修改在获取和执行下一条指令之前完成。

另请参阅:《IA-32 英特尔架构软件开发人员手册》第 3 卷AP-485《英特尔处理器标识和 CPUID 指令》第 7 章中的“序列化指令”。

从技术上讲,这并不能保证指令 B 不会在管道中启动。例如,处理器可能会在完成执行指令 A 之前解码并获取指令 B。然而,从程序员角度(即可观察的行为)来看,就好像指令 B 仅在指令 A 完成后才启动。