如何确保一条指令在流水线 CPU 上开始第二条指令之前完成?
How to ensure that one instruction is finished before a second instruction is started on a pipelined CPU?
假设有两条顺序指令,如下所示:
instruction A
instruction B
由于 CPU 流水线,B 将在 A 完成之前开始。
是否存在一种机制来确保 B 在 A 完成后启动?
更新:
很抱歉,我没有准确描述问题。我的意思是,这两条指令具有应用程序级的顺序依赖性,但没有危险。例如,在事务系统中,第一条指令是将日志刷新到持久存储,第二条指令是通知客户端事务提交。所以我们不能在第一条指令完成之前执行第二条指令。如何提供这个执行顺序?
Because of CPU pipelining, B will start before A finishes.
所以呢?为什么这是个问题?
在基本的pipelined architecture中,指令A会在第一个周期开始执行,然后指令B会在下一个周期开始执行。
以basic 5-stage RISC pipeline为例,它看起来是这样的:
Clock Cycle | 1 | 2 | 3 | 4 | 5 | 6 |
--------------|---------------------------------------------------------------------------
Instruction A | Fetch | Decode | Execute | Mem. Access | Writeback |
Instruction B | | Fetch | Decode | Execute | Mem. Access | Writeback |
处理器将在第一个时钟周期开始获取指令 A。在第二个时钟周期,它将开始解码指令 A,同时 同时 获取指令 B。依此类推,沿着管道向下。
之所以如此有效,是因为指令 获取 单元是一个完全独立于指令 解码 单元的硬件(即使两者都可以在同一个硅片上实现),所以同时占用这些单元中的每一个是有意义的。这是实现 instruction-level parallelism (ILP).
的一种机制
最终,您可以看到指令 A 将在第 5 个周期完成,而指令 B 将在第 6 个周期完成。不过,这比指令 A 在第 5 个周期完成而指令 B 无法 开始直到第6周期,推迟到第11周期完成。
处理器内部的逻辑处理指令依赖性,因此如果指令 B 以某种方式依赖于指令 A 的结果,处理器的解码器将能够检测到并停止执行指令 B,直到它的数据可用(即,直到指令 A 在流水线中走得足够远,其结果已准备就绪)。这一切都为您无缝处理,但它确实会引入性能成本 (pipeline bubbles),因此您希望尽可能避免它。这意味着编写您的代码,使具有依赖性的指令彼此尽可能分散,并在其间穿插独立的指令。
Does there exist a mechanism to ensure B starts after A finishes?
是的,这种机制通常存在,但您通常不想使用它们,因为它们会破坏管道的全部优势,从而减慢执行速度。
这些机制被称为序列化 指令(或有时称为“障碍”),因为它们建立了一个障碍,导致执行在特定点被序列化。
例如,在x86架构上,CPUID
指令是一个序列化指令(实际上是one of several)。所以你可以这样做:
Instruction A
CPUID
Instruction B
这将确保指令 B 在 指令 A 完成执行后才会开始。
来自英特尔架构手册:
CPUID
can be executed at any privilege level to serialize instruction execution. Serializing instruction execution guarantees that any modifications to flags, registers, and memory for previous instructions are completed before the next instruction is fetched and executed.
See also: "Serializing Instructions" in Chapter 7 of the IA-32 Intel Architecture Software Developer's Manual, Volume 3 AP-485, Intel Processor Identification and the CPUID Instruction.
从技术上讲,这并不能保证指令 B 不会在管道中启动。例如,处理器可能会在完成执行指令 A 之前解码和获取指令 B。但是,从 程序员 的角度(即可观察到的行为)来看,指令 B 就好像是仅在指令 A 完成后才开始。
有几种类型的序列化,仅用于说明,您需要序列化/记分板说明,这可以防止后辈在前一个提交之前进入 OOO 机器。 CPUID
这样做但是很重。其他一些指令也可以这样做(见下文)。
然后,有面向内存的机制来确保耗尽加载或存储缓冲区,主要用于内存排序目的。 LFENCE
和 SFENCE
分别保证,而 MFENCE
两者都保证。重要的是要注意这与指令序列化有些正交,例如英特尔的开发人员手册指出:
The MFENCE instruction is ordered with respect to all load and store
instructions, other MFENCE instructions, any LFENCE and SFENCE
instructions, and any serializing instructions (such as the CPUID
instruction). MFENCE does not serialize the instruction stream
可能有组合,例如我认为锁定操作(例如 locked inc)保证指令和内存序列化(第一个是由于内存排序,后者是由于原子性)。另见第 3A 卷第 8 章 - https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf
最后,您还需要一种方法来确保将数据写入内存(或某些情况下的持久存储。存储缓冲区耗尽仍然意味着数据可以驻留在本地缓存中。在正常的 WB 上内存这就足够了,因为任何其他观察者都必须窥探它并获取更新,但在某些情况下你想确保数据不会因崩溃而丢失。为此你可以使用 CLFLUSH/CLFLUSHOPT , 或 PCOMMIT (deprecated on some systems) / CLWB
同样 - 以上所有内容都有不同的含义,具体取决于您的需要。
假设有两条顺序指令,如下所示:
instruction A
instruction B
由于 CPU 流水线,B 将在 A 完成之前开始。
是否存在一种机制来确保 B 在 A 完成后启动?
更新:
很抱歉,我没有准确描述问题。我的意思是,这两条指令具有应用程序级的顺序依赖性,但没有危险。例如,在事务系统中,第一条指令是将日志刷新到持久存储,第二条指令是通知客户端事务提交。所以我们不能在第一条指令完成之前执行第二条指令。如何提供这个执行顺序?
Because of CPU pipelining, B will start before A finishes.
所以呢?为什么这是个问题?
在基本的pipelined architecture中,指令A会在第一个周期开始执行,然后指令B会在下一个周期开始执行。
以basic 5-stage RISC pipeline为例,它看起来是这样的:
Clock Cycle | 1 | 2 | 3 | 4 | 5 | 6 |
--------------|---------------------------------------------------------------------------
Instruction A | Fetch | Decode | Execute | Mem. Access | Writeback |
Instruction B | | Fetch | Decode | Execute | Mem. Access | Writeback |
处理器将在第一个时钟周期开始获取指令 A。在第二个时钟周期,它将开始解码指令 A,同时 同时 获取指令 B。依此类推,沿着管道向下。
之所以如此有效,是因为指令 获取 单元是一个完全独立于指令 解码 单元的硬件(即使两者都可以在同一个硅片上实现),所以同时占用这些单元中的每一个是有意义的。这是实现 instruction-level parallelism (ILP).
的一种机制最终,您可以看到指令 A 将在第 5 个周期完成,而指令 B 将在第 6 个周期完成。不过,这比指令 A 在第 5 个周期完成而指令 B 无法 开始直到第6周期,推迟到第11周期完成。
处理器内部的逻辑处理指令依赖性,因此如果指令 B 以某种方式依赖于指令 A 的结果,处理器的解码器将能够检测到并停止执行指令 B,直到它的数据可用(即,直到指令 A 在流水线中走得足够远,其结果已准备就绪)。这一切都为您无缝处理,但它确实会引入性能成本 (pipeline bubbles),因此您希望尽可能避免它。这意味着编写您的代码,使具有依赖性的指令彼此尽可能分散,并在其间穿插独立的指令。
Does there exist a mechanism to ensure B starts after A finishes?
是的,这种机制通常存在,但您通常不想使用它们,因为它们会破坏管道的全部优势,从而减慢执行速度。
这些机制被称为序列化 指令(或有时称为“障碍”),因为它们建立了一个障碍,导致执行在特定点被序列化。
例如,在x86架构上,CPUID
指令是一个序列化指令(实际上是one of several)。所以你可以这样做:
Instruction A
CPUID
Instruction B
这将确保指令 B 在 指令 A 完成执行后才会开始。
来自英特尔架构手册:
CPUID
can be executed at any privilege level to serialize instruction execution. Serializing instruction execution guarantees that any modifications to flags, registers, and memory for previous instructions are completed before the next instruction is fetched and executed.See also: "Serializing Instructions" in Chapter 7 of the IA-32 Intel Architecture Software Developer's Manual, Volume 3 AP-485, Intel Processor Identification and the CPUID Instruction.
从技术上讲,这并不能保证指令 B 不会在管道中启动。例如,处理器可能会在完成执行指令 A 之前解码和获取指令 B。但是,从 程序员 的角度(即可观察到的行为)来看,指令 B 就好像是仅在指令 A 完成后才开始。
有几种类型的序列化,仅用于说明,您需要序列化/记分板说明,这可以防止后辈在前一个提交之前进入 OOO 机器。 CPUID
这样做但是很重。其他一些指令也可以这样做(见下文)。
然后,有面向内存的机制来确保耗尽加载或存储缓冲区,主要用于内存排序目的。 LFENCE
和 SFENCE
分别保证,而 MFENCE
两者都保证。重要的是要注意这与指令序列化有些正交,例如英特尔的开发人员手册指出:
The MFENCE instruction is ordered with respect to all load and store instructions, other MFENCE instructions, any LFENCE and SFENCE instructions, and any serializing instructions (such as the CPUID instruction). MFENCE does not serialize the instruction stream
可能有组合,例如我认为锁定操作(例如 locked inc)保证指令和内存序列化(第一个是由于内存排序,后者是由于原子性)。另见第 3A 卷第 8 章 - https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf
最后,您还需要一种方法来确保将数据写入内存(或某些情况下的持久存储。存储缓冲区耗尽仍然意味着数据可以驻留在本地缓存中。在正常的 WB 上内存这就足够了,因为任何其他观察者都必须窥探它并获取更新,但在某些情况下你想确保数据不会因崩溃而丢失。为此你可以使用 CLFLUSH/CLFLUSHOPT , 或 PCOMMIT (deprecated on some systems) / CLWB
同样 - 以上所有内容都有不同的含义,具体取决于您的需要。