指令管线化

指令管线化（Instruction pipeline）是为了让计算机和其它数位电子装置能够加速指令的通过速度（单位时间内被执行的指令数量）而设计的技术。

管线在处理器的内部被组织成层级，各个层级的管线能半独立地单独运作。每一个层级都被管理并且链接到一条“链”，因而每个层级的输出被送到其它层级直至任务完成。处理器的这种组织方式能使总体的处理时间显著缩短。

未管线化的架构产生的效率低，因为有些CPU的模组在其他模组执行时是闲置的。管线化虽并不会完全消除CPU的闲置时间，但是能够让这些模组并行运作而大幅提升程式执行的效率。差不多有些类似流水线工厂的概念。

但并不是所有的指令都是独立的。在一条简单的管线中，完成一个指令可能需要5层。如右图所示，要在最佳性能下运算，当第一个指令被执行时，这个管线需要运行随后4条独立的指令。可是，如果随后4条指令依赖于第一条指令的输出，管线控制逻辑器，就必须插入延迟时脉周期到管线内，直到依赖被满足。而转发技术能显著减少延时。凭借多个层，虽然管线化在理论上能提高效能，优胜于无管线的内核（假设时脉也因应层的数量按比例增加），但事实上，许多指令码设计中并不会考虑到理想的执行。

## 简介

<image>

管线化是假设程式执行时有一连串的指令要被执行（垂直座标i是指令集，水平座标表时间t）。绝大多数当代的CPU都是利用时脉驱动。

而CPU是由内部的逻辑闸与正反器组成。当受到时脉触发时，正反器得到新的数值，并且逻辑闸需要一段时间来解析出新的数值，而当受到下一个时脉触发时正反器又得到新的数值，以此类推。而借由逻辑闸分散成很多小区块，再让正反器链接这些小区块组，使逻辑闸输出正确数值的时间延迟得以减少，这样一来就可以减少指令执行所需要的周期。

举例来说，典型的RISC管线被分解成五个阶段，每个阶段之间使用正反器链接。

- 读取指令
- 指令解码与读取暂存器
- 执行
- 内存存取
- 写回暂存器

## 优缺点

并非在所有情况下管线技术都起作用。可能有一些缺点。如果一条指令管线能够在每一个时脉周期接纳一条新的指令，被称为完整管线化（fully pipelined）。因管线中的指令需要延迟处理而要等待数个时脉周期，被称为非完整管线化。

当一名程序员（或者组合者/编译者）编写组合代码（或者汇编码）时，他们会假定每个指令是循序执行的。而这个假设会使管线化无效。当此现象发生后程式会表现的不正常，而此现象就是危害。不过目前有提供几种技术来解决这些危害像是转发与延迟等。

### 优点

- 减少了处理器执行指令所需要的时脉周期，在通常情况下增加了指令的输入频率（issue-rate）。
- 一些集成电路（combinational circuits），例如加法器（adders）或者乘法器（multipliers），通过添加更多的环路（circuitry）使其工作得更快。如果以管线化替代，能相对地减少环路。

### 缺点

- 非管线化的处理器每次（at a time）只执行一个指令。防止分支延时（事实上，每个分支都会产生延时）和串行指令被并行执行产生的问题。设计比较简单和较低生产成本。
- 在执行相同的指令时，非管线化处理器的指令传输延迟时间（The instruction latency）比管线化处理器明显较短。这是因为管线化的处理器必须在数据路径（data path）中添加额外正反器（flip-flops）。
- 非管线化处理器有固定指令位宽（a stable instruction bandwidth）。管线化处理器的性能更难以预测，并且不同的程序之间的变化（vary）可能更大。

## 示例

### 一般的管线

<image>

右图是一般有4层管线的示意图：

- 读取指令（Fetch）
- 指令解码（Decode）
- 执行指令（Execute）
- 写回执行结果（Write-back）

上方的大灰色格是一连串未执行的指令；下方的大灰色格则是已执行完成的指令；中间的大白色格则是管线。

执行顺序如以下列表所示：

时序｜执行情况

0｜四条指令等待执行

1｜、从内存（memory）中读取绿色指令

2｜、绿色指令被解码、从主存储器中读取紫色指令

3｜、绿色指令被执行（事实上运算已经开始（performed））、紫色指令被解码、从主存储器中读取蓝色指令

4｜、绿色指令的运算结果被写回到寄存器（register）或者主存储器、紫色指令被执行、蓝色指令被解码、从主存储器中读取红色指令

5｜、绿色指令被执行完毕、紫色指令的运算结果被写回到寄存器或者主存储器、蓝色指令被执行、红色指令被解码

6｜、紫色指令被执行完毕、蓝色指令的运算结果被写回到寄存器或者主存储器、红色指令被执行

7｜、蓝色指令被执行完毕、红色指令的运算结果被写回到寄存器或者主存储器

8｜、红色指令被执行完毕

9｜所有指令皆执行完毕

#### 汽泡

<image>

指令执行中产生一个“打嗝”（hiccup），在管线中生成一个没有实效的气泡。

如右图，在编号为2的时脉周期中，紫色指令的读取被延迟，并且在编号为3的时脉周期中解码层也产生了一个气泡。所有在紫色指令之后的指令都被延迟执行，而在其之前已经执行了的指令则不受影响。

由于气泡使指令执行延迟了一个时脉周期，完成全部4条指令的执行共需要8个时脉周期。

而气泡处对指令的读取、解码、执行与写回都没有实质影响。这可以使用nop代码来完成。

## 复杂化

很多处理器的管线深度到5层、7层、10层，甚至31层（像是Intel Pentium 4 Prescott）。Xelerator X10q甚至有多于1000层的管线深度。

微架构 （Microarchitecture）｜管线层数 （Pipeline stages）

Sony Cell｜23

IBM PowerPC 7｜17

IBM Xenon｜19

AMD Athlon｜10

AMD Athlon XP｜11

AMD Athlon 64｜12

AMD Phenom｜12

AMD Opteron｜15

ARM7TDMI (-S)｜3

ARM7EJ-S｜5

ARM810｜5

ARM9TDMI｜5

ARM1020E｜6

XScale PXA210/PXA250｜7

ARM1136J (F)-S｜8

ARM1156T2 (F)-S｜9

ARM Cortex-A5｜8

ARM Cortex-A8｜13

AVR32 AP7｜7

AVR32 UC3｜3

DLX｜5

Intel P5（Pentium）｜5

Intel P6（Pentium Pro）｜14

Intel P6（Pentium III）｜10

Intel NetBurst（Willamette）｜20

Intel NetBurst（Northwood）｜20

Intel NetBurst（Prescott）｜31

Intel NetBurst（Cedar Mill）｜31

Intel Core｜14

Intel Atom｜16

LatticeMico32｜6

R4000｜8

StrongARM SA-110｜5

SuperH SH2｜5

SuperH SH2A｜5

SuperH SH4｜5

SuperH SH4A｜7

UltraSPARC｜9

UltraSPARC T1｜6

UltraSPARC T2｜8

WinChip｜4

LC2200 32 bit｜5

当程式出现分支将不利于过深管线，整条管线将会无效化。为了减轻此状况，分支预测就变的重要。如果分支预测错误，也能够借由自行结束预测来避免加速恶化效率。在某些运用上，像是超级电脑运算，为了能够将超长管线的运算优势凸显出来，会特地将程式写的极少分支化来避免预测失败，而且深度的管线化主要是为了能降低每个时脉执行的指令量而设计。当程式经常出现分支，把分支重新排序（像是将更为需要的指令提早放入管线中）而将明显的降低损失的速度以避免将分支“冲垮”。像是gcov程式能够使用一种覆盖率检查的技术检查特定分支的执行频率，但是这种检查法经常是最佳化的最后手段。处理能力高的管线会因为很多分支的程式而降低效率，这是因为处理器不知道下一个要读取的指令是什么，而需要等待完成分支指令而让管线清空。处理完分支之后，下一个指令就要经过所有管线，直到整个指令集的结果出现，而处理器才会再继续执行。而在极端的状况下，管线化处理器的效能理论上可能会与未管线化处理器一致，甚至是每层管线都在待命状态，而且指令经常在管线之中跑来跑去时的效能比较差一些。

由于指令管线化，处理器读取机器码时并不会立即执行。因为如此，在很接近的地方执行更新机器码的动作就可能无法作用，因为这些机器码已经进入预读输入队列内。指令快取又会让此现象更加恶化。不过这只会在能够自我变更的程式出现此现象。

## 范例

### 范例一

一个典型的加法指令可能会写成像`ADD A, B, C`，而中央处理器（CPU）会将内存（Memory）内A位置与B位置的数值相加后放到C位置。在管线化处理器内，管线控制器会将这个指令分拆成一连串微指令：

```
LOAD R1, A
LOAD R2, B 
ADD R3, R1, R2
STORE R3, C
LOAD next instruction
```

R1, R2和R3是CPU内的暂存器（register是CPU里面能够快速存取的暂存内存）。主存储器内标注为A位置和B位置之存储单元中的数值被载入（或称复制）到暂存器R1和R2中，然后送到加法器中相加，结果输出到暂存器R3中，R3中的数值再被存储到主存储器内标注为C位置的存储单元。

而且在非管线化的例子，开始驱动加法动作到完成的时间是不变的。

在这个范例中的管线分为3层：载入，执行，存储。每一步被称为管线层（或称管线阶段，pipeline stages）。

在非管线化处理器中，同一时间只允许一个层运作，所以必须等待指令执行完毕才能开始执行下一条指令。在管线化处理器中，所有的层能在同一时间处理不同的指令。当一条指令在执行层，另外一条指令在解码层，第三条指令在读取层。

管线没有减少执行指令所花费的时间；它增加了在同一时间被处理的指令数量，并且减少了完成指令之间的延迟。随着处理器中管线层的数量增加，能在同一时间被处理的指令数量也相应增加，也减少了指令等待处理所产生的延迟。现在生产的微处理器至少有2层管线。来源请求（Atmel AVR与PIC微控制器都有2层管线）Intel Pentium 4处理器有20层管线。

### 范例二

以下表格具体列出3层管线理论：

管线层（Stage）｜说明（Description）

读取（Load）｜从主存储器中读取指令

执行（Execute）｜执行指令

存储（Store）｜将执行结果存储到主存储器和/或者暂存器

以组合语言表示将会被执行的指令列表：

```
	LOAD  A, #40      ;讀取40載入A內
	MOVE  B, A        ;將A內的数据移动到B內
	ADD   B, #20      ;將B內的數據與20相加
	STORE 0x300, B    ;將B內的數據儲存到地址為0x300的存儲器單元
```

代码的执行循序如下：

读取｜执行｜储存

LOAD

从主存储器中读取LOAD指令。

读取｜执行｜储存

MOVE｜LOAD

LOAD指令被执行，同时从主存储器中读取MOVE指令。

读取｜执行｜储存

ADD｜MOVE｜LOAD

LOAD指令在存储层（Store stage），LOAD指令的执行结果#40（the number 40）将被存储到暂存器A。同时，MOVE指令被执行。MOVE指令必须等待LOAD指令执行完毕才能将暂存器A中的内容移动到暂存器B中。

读取｜执行｜储存

STORE｜ADD｜MOVE

STORE指令被载入，同时MOVE指令执行完毕，并且ADD指令被执行。

注意! 有时候，一个指令会依赖于其他指令的执行结构（例如以上的MOVE指令）。当一个指令因为操作数而需引用一个特定的位置，读取（作为输入）或者写入（作为输入），执行那些指令的循序不同于程序原本的执行循序能导致冒险（hazards）。现时有机种技术用于预防危害，或者绕过（working around）它们。

## 外部链接

- 来自ArsTechnica的管线化相关文章