Link 指令流水线和每条指令的周期数之间
Link between instruction pipelining and cycles per instruction
我明白了instruction pipelining的基本原理。
我还了解到某些指令可能需要更长时间才能执行 (cycles per instruction)。
但我不明白两者之间的 link。
我看到的所有流水线图似乎都有"perfect"条指令,它们的长度(循环数)都相同。
但是如果第一条指令确实需要 5 个周期,而第二条指令需要 3 个周期怎么办? cpu 会停止 2 个周期吗?
这个摊位会被称为 bubble ? Or is this different from hazards 和数据依赖吗?
此外,指令的长度(以字节为单位)有任何影响吗?
它实际上比你想象的要复杂一些。
一是CPU不执行指令,而是执行微指令,二是可以乱序执行微指令。
uops
一个简单的指令转换为一个 uop,一个复杂的指令被分成多个 uop。 CPU 有一个 uop 缓存,可以保留最后(例如 1024)几个 uops。微指令彼此之间比完整指令更相似,因此在流水线中配对得更好。
乱序执行
如果 CPU 需要等待计算结果,它会查找不依赖于前一条指令的微指令并执行这些微指令。
为了允许 OoO 执行,CPU 有一个寄存器文件,其中包含比程序员可用的更多的寄存器(例如 256 个通用寄存器)。它可以将其用作暂存器来存储中间结果。
所有执行的指令都进入退休缓冲区,结果按原始顺序输出。
缓冲区
除此之外,停顿问题由缓冲区解决。
指令以推测方式获取,并位于缓冲区中等待解码。
恒定时间解码
X86/X64 以其复杂的解码而臭名昭著。 AMD 和 Intel 都通过将大量芯片用于解码问题来解决这个问题,这样他们的 CPU 就可以在每个周期解码恒定数量的字节,而不受指令复杂性的影响。指令的长度并不重要,因为时间关键代码(紧密循环)是从 uop 缓存中执行的,不需要解码。此外,解码通常尺寸过大,因此几乎可以肯定不会成为瓶颈。
更多阶段
现代 CPU 有 14 个或更多阶段,而不是您似乎设想的 4 个。
例如,参见 AMD Zen 架构的说明:https://www.extremetech.com/computing/234354-a-state-of-zen-amd-unveils-new-architectural-details-on-its-latest-cpu-core
因此,除了管道之外,还有很多其他过程发生,所有这些过程都是为了防止停顿和填充气泡。
实际上,现代处理器在配对具有不同延迟的指令时不会受到影响。低延迟微指令的使用在很大程度上消除了这个问题。
危险
您 link 的维基百科文章解释得很好。现代 CPU 使用 Tomasulo's algorithm with register renaming 来防止气泡。
你在问题中提到了很多东西,所以我会投入 2 美分来尝试让它更清楚一些。让我们以有序 MIPS 架构为例 - 它具有您提到的所有内容,但可变长度指令除外。
许多 MIPS CPUs 有 5 级流水线与阶段:IF -> ID -> EX -> MEM -> WB
。 (https://en.wikipedia.org/wiki/Classic_RISC_pipeline)。让我们首先看一下这些指令,其中每个阶段通常需要一个时钟周期(例如,缓存未命中可能不是这种情况)。例如,SW(将字存储到内存)、BNEZ(非零分支)和 ADD(将两个寄存器相加并存储到寄存器)。并非所有这些指令在所有流水线阶段都有用。例如SW在WB阶段没有工作可做,BNEZ最早可以在ID阶段完成(即最早可以计算出目标地址),ADD在MEM阶段没有工作。
无论如何,这些指令中的每一个都将通过管道的每个阶段,即使它们在其中一些阶段没有工作。该指令将占用给定的阶段,但不会完成任何实际工作(即没有结果写入 WB 阶段的寄存器以用于 SW 指令)。也就是说,这种情况下不会有档位。
转向更复杂的指令,其 EX 阶段可能需要数十个周期,例如 MUL 或 DIV。这里的事情变得更加棘手。现在指令可以乱序完成,即使它们总是按顺序获取(意味着 WAW hazards 现在是可能的)。举个例子:
MUL R1, R10, R11
ADD R2, R5, R6
MUL 首先被获取,它在 ADD 之前到达 EX 阶段,但是 ADD 将在 MUL 的 EX 阶段运行s 之前完成超过 10 个时钟周期。但是,流水线不会在任何时候停止,因为在此序列中不可能出现危险 - RAW 和 WAW 危险都不可能。再举个例子:
MUL R1, R10, R11
ADD R1, R5, R6
现在MUL和ADD写同一个寄存器。由于 ADD 将比 MUL 更早完成,因此它将完成 WB 并写入其结果。稍后,MUL 会做同样的事情,而 R1 最终会得到错误的(旧的)值。这是需要管道 stall 的地方。解决这个问题的一种方法是在 MUL 进入 MEM 阶段之前阻止 ADD 发行(从 ID 阶段移动到 EX 阶段)。这是通过冻结或停止管道来完成的。引入浮点运算导致流水线出现类似问题
我将通过触及固定长度与可变长度指令格式的主题来完成我的回答(即使您没有明确要求)。 MIPS(和大多数 RISC)CPUs 有固定长度的编码。这极大地简化了 CPU 流水线的实现,因为可以在单个周期内解码指令和读取输入寄存器(假设寄存器位置固定在给定的指令格式中,这对于 MIPS 是正确的)。此外,由于指令始终具有相同的长度,因此简化了提取,因此无需开始解码指令来查找其长度。
当然也有缺点:生成紧凑二进制文件的可能性降低,导致程序较大,进而导致缓存性能较差。此外,内存流量增加以及更多字节的数据是 read/written from/to 内存,这对于节能平台可能很重要。
这一优势导致某些 RISC 架构定义了 16 位指令长度模式(MIPS16 or ARM Thumb), or even a variable-length instruction set (ARM Thumb2 有 16 位和 32 位指令)。与 x86 不同,Thumb2 的设计目的是使快速确定指令长度变得容易,因此 CPUs 仍然很容易解码。
这些压缩的 ISA 通常需要更多的指令来实现相同的程序,但如果代码获取比流水线中的指令吞吐量更成为瓶颈,那么总共 space 和 运行 更快。 (小/不存在的指令缓存,and/or 从嵌入式 CPU 中的 ROM 读取)。
我明白了instruction pipelining的基本原理。
我还了解到某些指令可能需要更长时间才能执行 (cycles per instruction)。
但我不明白两者之间的 link。
我看到的所有流水线图似乎都有"perfect"条指令,它们的长度(循环数)都相同。
但是如果第一条指令确实需要 5 个周期,而第二条指令需要 3 个周期怎么办? cpu 会停止 2 个周期吗?
这个摊位会被称为 bubble ? Or is this different from hazards 和数据依赖吗?
此外,指令的长度(以字节为单位)有任何影响吗?
它实际上比你想象的要复杂一些。
一是CPU不执行指令,而是执行微指令,二是可以乱序执行微指令。
uops
一个简单的指令转换为一个 uop,一个复杂的指令被分成多个 uop。 CPU 有一个 uop 缓存,可以保留最后(例如 1024)几个 uops。微指令彼此之间比完整指令更相似,因此在流水线中配对得更好。
乱序执行
如果 CPU 需要等待计算结果,它会查找不依赖于前一条指令的微指令并执行这些微指令。
为了允许 OoO 执行,CPU 有一个寄存器文件,其中包含比程序员可用的更多的寄存器(例如 256 个通用寄存器)。它可以将其用作暂存器来存储中间结果。
所有执行的指令都进入退休缓冲区,结果按原始顺序输出。
缓冲区
除此之外,停顿问题由缓冲区解决。
指令以推测方式获取,并位于缓冲区中等待解码。
恒定时间解码
X86/X64 以其复杂的解码而臭名昭著。 AMD 和 Intel 都通过将大量芯片用于解码问题来解决这个问题,这样他们的 CPU 就可以在每个周期解码恒定数量的字节,而不受指令复杂性的影响。指令的长度并不重要,因为时间关键代码(紧密循环)是从 uop 缓存中执行的,不需要解码。此外,解码通常尺寸过大,因此几乎可以肯定不会成为瓶颈。
更多阶段
现代 CPU 有 14 个或更多阶段,而不是您似乎设想的 4 个。
例如,参见 AMD Zen 架构的说明:https://www.extremetech.com/computing/234354-a-state-of-zen-amd-unveils-new-architectural-details-on-its-latest-cpu-core
因此,除了管道之外,还有很多其他过程发生,所有这些过程都是为了防止停顿和填充气泡。
实际上,现代处理器在配对具有不同延迟的指令时不会受到影响。低延迟微指令的使用在很大程度上消除了这个问题。
危险
您 link 的维基百科文章解释得很好。现代 CPU 使用 Tomasulo's algorithm with register renaming 来防止气泡。
你在问题中提到了很多东西,所以我会投入 2 美分来尝试让它更清楚一些。让我们以有序 MIPS 架构为例 - 它具有您提到的所有内容,但可变长度指令除外。
许多 MIPS CPUs 有 5 级流水线与阶段:IF -> ID -> EX -> MEM -> WB
。 (https://en.wikipedia.org/wiki/Classic_RISC_pipeline)。让我们首先看一下这些指令,其中每个阶段通常需要一个时钟周期(例如,缓存未命中可能不是这种情况)。例如,SW(将字存储到内存)、BNEZ(非零分支)和 ADD(将两个寄存器相加并存储到寄存器)。并非所有这些指令在所有流水线阶段都有用。例如SW在WB阶段没有工作可做,BNEZ最早可以在ID阶段完成(即最早可以计算出目标地址),ADD在MEM阶段没有工作。
无论如何,这些指令中的每一个都将通过管道的每个阶段,即使它们在其中一些阶段没有工作。该指令将占用给定的阶段,但不会完成任何实际工作(即没有结果写入 WB 阶段的寄存器以用于 SW 指令)。也就是说,这种情况下不会有档位。
转向更复杂的指令,其 EX 阶段可能需要数十个周期,例如 MUL 或 DIV。这里的事情变得更加棘手。现在指令可以乱序完成,即使它们总是按顺序获取(意味着 WAW hazards 现在是可能的)。举个例子:
MUL R1, R10, R11
ADD R2, R5, R6
MUL 首先被获取,它在 ADD 之前到达 EX 阶段,但是 ADD 将在 MUL 的 EX 阶段运行s 之前完成超过 10 个时钟周期。但是,流水线不会在任何时候停止,因为在此序列中不可能出现危险 - RAW 和 WAW 危险都不可能。再举个例子:
MUL R1, R10, R11
ADD R1, R5, R6
现在MUL和ADD写同一个寄存器。由于 ADD 将比 MUL 更早完成,因此它将完成 WB 并写入其结果。稍后,MUL 会做同样的事情,而 R1 最终会得到错误的(旧的)值。这是需要管道 stall 的地方。解决这个问题的一种方法是在 MUL 进入 MEM 阶段之前阻止 ADD 发行(从 ID 阶段移动到 EX 阶段)。这是通过冻结或停止管道来完成的。引入浮点运算导致流水线出现类似问题
我将通过触及固定长度与可变长度指令格式的主题来完成我的回答(即使您没有明确要求)。 MIPS(和大多数 RISC)CPUs 有固定长度的编码。这极大地简化了 CPU 流水线的实现,因为可以在单个周期内解码指令和读取输入寄存器(假设寄存器位置固定在给定的指令格式中,这对于 MIPS 是正确的)。此外,由于指令始终具有相同的长度,因此简化了提取,因此无需开始解码指令来查找其长度。
当然也有缺点:生成紧凑二进制文件的可能性降低,导致程序较大,进而导致缓存性能较差。此外,内存流量增加以及更多字节的数据是 read/written from/to 内存,这对于节能平台可能很重要。
这一优势导致某些 RISC 架构定义了 16 位指令长度模式(MIPS16 or ARM Thumb), or even a variable-length instruction set (ARM Thumb2 有 16 位和 32 位指令)。与 x86 不同,Thumb2 的设计目的是使快速确定指令长度变得容易,因此 CPUs 仍然很容易解码。
这些压缩的 ISA 通常需要更多的指令来实现相同的程序,但如果代码获取比流水线中的指令吞吐量更成为瓶颈,那么总共 space 和 运行 更快。 (小/不存在的指令缓存,and/or 从嵌入式 CPU 中的 ROM 读取)。