目录
对于计算机系统而言,提升器件性能和改进系统结构,是提高整体性能的两大途径。指令流水线 就是改进处理器架构的一项并行处理技术,可以极大地提高 CPU 的工作效率。
计算机中的并行性体现在不同的级别上。通常可以分为以下 4 个级别:
-
作业级/程序级
-
任务级/进程级
-
指令之间级
-
指令内部级
前两个级别是粗粒度的,又称为 过程级,一般用软件算法实现;而后两个级别是细粒度的,又称为 指令级,一般用硬件实现。指令流水线就是一项实现指令级并行的技术。
一、指令流水线的基本概念和原理
1.指令流水线的概念和原理
类似于工厂中流水装配线的思想,指令的执行过程也可以分成不同的阶段,每个阶段需要的 CPU 部件是不同的;所以 CPU 各个部件可以同时对不同的指令进行处理,这就是指令流水线。
如果只把指令处理过程分成两个阶段:取指令和执行指令,那么没有采用流水线的计算机会串行处理每条指令:
取指令的操作可以由取指部件完成,执行指令的操作则可以由执行部件完成。所以这种顺序执行的方式尽管实现简单,但是对硬件的利用率不高。
如果采用流水线,则可以在时间上将不同指令的执行 ”重叠“ 起来,实现并行的效果:
划分两个阶段,可以同时有两条指令重叠,这称为指令的 二级流水。
理论上讲,二级流水线同时有两条指令执行,相当于可以将指令周期减半、速度提升一倍。
2.六级流水线
为了进一步提升处理速度,可以将指令的处理过程划分成更为细致的几个阶段:
-
取指(FI):从主存取出一条指令,并暂存在缓冲区(IR)中。
-
指令译码(DI):确定操作方式和操作数的寻址方式。
-
计算操作数地址(CO):根据寻址方式,计算操作数的有效地址。
-
取操作数(FO):从主存中取出操作数(如果在寄存器中,则可以跳过这个阶段)。
-
执行指令(EI):执行指令的具体操作,将结果存放在目的位置(寄存器)。
-
写操作数(WO):将结果写入主存。
共有 6 个阶段,所以可以构建出六级流水线。流水线中各阶段应该有相同的机器周期,这里我们可以假设以上各段时间相同。对应的指令六级流水时序图如下:
这样的流水线处理器中,应该设计 6 个操作部件,可以同时处理 6 条指令,从而大大提高了程序的运行速度。假设机器周期就等于时钟周期,也就是每个阶段用时是 1 个时钟周期,那么流水线可以做到 CPI 接近于1。
当然,实际的流水线远远达不到这样的性能。流水线也有很多问题:
-
实际处理器中,每条指令不一定包含了完整的 6 个阶段;
-
实际的处理器中,指令处理的各个阶段时间不会相同;
-
这里需要假设不存在同时访存的冲突,所以所有指令都可以同时并行;实际情况下,指令可能存在冲突;
-
当遇到条件转移指令时,下一条指令是无法提前确定的,只能根据前一条指令的执行结果来判断,时间上会有损失。
3.流水线处理器的设计原则
流水线处理器设计的基本原则,就是:
-
机器周期应该是定长的,并且以用时最长的处理阶段为准;它对应着每个流水线阶段(简称 ”流水段“ )的时间长度。
-
流水段的个数,应该以最复杂指令的处理阶段数量为准;
很明显,如果 CPU 采用的指令集比较复杂,流水线的效率就会大打折扣。所以更有利于实现流水线的指令集架构,应该满足:
-
尽量采用定长指令字;
-
尽量简化指令的功能,每条指令只完成最基本的功能;
-
简化寻址方式,每条指令都可以在较短的时间内得到需要的有效地址;
-
使用 load / store 体系结构,只有 load / store 指令可以进行访存操作;
-
使用大量寄存器,尽量减少访存操作。
可以看出,这些都是 RISC 的特点。所以说,RISC 更加有利于实现流水线;而具体实现中,一般会将每个阶段的机器周期设为一个时钟周期,这样大部分指令都可以在一个时钟周期完成完成。
二、流水线冒险
在指令流水线中,往往会出现一些造成 ”断流“ 的情况,导致流水线无法正确处理指令,这就是 流水线冒险。
流水线冒险主要有三种:结构冒险、数据冒险 和 控制冒险。
我们可以先考虑一个具体的案例。假设现在有一个五级流水线,5个阶段设计分别为:取指令(FI)、指令译码/读寄存器(ID)、执行/计算有效地址(EX)、访存(MEM)、结果写回寄存器(WB)。那么不同类型的指令,在各流水段的操作也有所不同:
接下来考虑三种不同的流水线冒险。
1.结构冒险
当流水线中多条指令重叠执行时,不同指令可能会争用同一功能部件而产生资源冲突,这就是 结构冒险,也称为资源相关。
例如,取指阶段 IF 和访存阶段 MEM 都需要访问主存,而冯诺依曼架构的计算机大多会把指令和数据保存在同一个存储器中、且只有唯一的访问口。那么如果在某个时钟周期内,流水线上某一条指令处在 IF 阶段、另一条指令处在 MEM 阶段,就会发生访存冲突。
上表中,在第 4 个时钟周期,第 i 条指令 LOAD 处于 MEM 段,正在访问主存;而同时第 i + 3 条指令处于 IF 段,也需要访问主存取指:于是产生了冲突。
解决方案是可以让后一条指令暂停一个时钟周期,等前一条指令完成访存操作后,再开始取指。
另一种方案是,可以设置两个独立的存储器,分别存放指令和数据,这样就可以从根本上避免冲突。另外也可以采用 ”指令预取“ 技术,在 CPU 中设置指令队列,将指令预先取出来放到队列中排队。
2.数据冒险
在流水线中,指令之间可能会有数据的关联。如果一条指令的执行,需要用到之前指令的计算结果;那么当之前的指令尚未执行结束时,下一条指令就直接开始读取数据,就会产生冲突。这种情况被称为 数据冒险。
例如,流水线中有下面两条连续的指令:
ADD R1,R2,R3 (R2)+(R3)→ R1
SUB R5,R1,R4 (R1)-(R4)→ R5
这里,第一条指令将 R2 和 R3 中的数据相加,结果放入 R1;然后第二条指令又将 R1 中的数据取出,跟 R4的值做减法,结果写入 R5。
在不采用流水线时,按照顺序执行是完全没有问题的;但当采用了流水线结构后,这种 ”先写后读“ 的顺序就发生了变化:
在第 3 个时钟周期,SUB 指令就开进入指令译码、读取寄存器 R1的数据了;而要到第 5 个时钟周期,ADD 指令才会将真正的计算结果写回。”先写后读“ 就变成了 ”先读后写“,产生了数据相关的冲突。
根据指令间对数据读写操作的先后顺序,数据冒险可以分成三类:
-
写后读(Read After Write,RAW):也就是先写后读,如果试图写入前就读取,就会读出错误的 旧 内容;
-
读后写(Write After Read,WAR):也就是先读后写,如果试图读取前就写入,就会读出错误的 新 内容;
-
写后写(Write After Write,WAW):两次连续的写入,如果改变了写入顺序,最后保存的数据就是先写入的值;
如果只考虑按顺序流动的流水线,那其实只会出现 RAW 相关的情况;而假如是非按序流动的流水线,允许后面的指令超过前面的指令、先流出流水线,那就还可能发生 WAR 和 WAW 的情况。
解决数据冒险可以采用下面的方法:
(1)后推法
要想解决数据冒险,最简单的解决方案,还是先将后面的指令暂停,等前面指令完成、生成所需的结果之后再继续进行。这种方法称为 后推法。
例如,我们再增加几个指令,构成一个指令序列:
ADD R1,R2,R3 (R2)+(R3)→ R1
SUB R5,R1,R4 (R1)-(R4)→ R5
AND R7,R1,R6 (R1)AND(R6)→ R7
OR R9,R1,R8 (R1)OR(R8)→ R9
XOR R11,R1,R10 (R1)AND(R10)→ R11
第一条 ADD 指令将 R2 和 R3 相加的结果写入 R1,之后的 4 条指令 SUB、AND、OR、XOR 都要使用 R1 中的值作为一个源操作数。这里就出现了 RAW 数据冒险。
如果采用后推法,将后续指令延迟到 ADD 指令完成写回 WB 阶段之后,就可以解决数据冒险:
(2)专用通路技术(数据旁路技术)
另一种解决方案是采用定向技术,也叫 专用通路技术 或 旁路技术。
基本思想是,上一条指令的运行结果,在 EX 阶段就已经产生;那就不必等到上一条指令后续阶段全部完成(写回寄存器),可以直接将结果送到后续指令需要的地方。这样流水线就可以不发生停顿。
由于需要对后续指令进行数据的定向传送操作,所以应该加入另外的部件。
上图就是一个带有旁路技术的 ALU 部件。ADD 指令执行的结果,会存入 暂存器 中;而暂存器的结果又会通过旁路通道,经多路开关直接送回 ALU 参与后续的计算。这里的定向传送,只发生在 ALU 内部。
3.控制冒险
控制冒险 主要是由 转移指令 引起的。当遇到条件转移指令(分支指令)时,由于只有上一条指令执行结束才能知道是否跳转,因此一般我们只能采用猜测法,默认不发生跳转、继续取下一条指令;如果真的发生了跳转,这时之前的操作全部作废,需要重新按照 PC 跳转的位置取指执行。这就破坏了流水线的连续流动。
还是用之前的六级流水线的例子,假设指令 3 是一条条件转移指令。
指令 3 是条件转移指令,所以只有在第 7 个时钟周期(时间单元)、指令 2 执行完毕之后才能判断是否进行跳转:如果不跳转,则继续执行指令 4;如果跳转,则执行指令 15。
采用猜测法,默认不跳转,所以流水线继续取指令 4,并正常向前流动;但当到第 7 个时钟周期时,发现结果满足条件,需要进行跳转,则之前第 4、5、6、7 个时钟周期所做的操作全部作废,第 8 个时钟周期重新取指令 15,继续流水线的流动。在第 9 ~12 个时钟周期内,没有指令完成,这是预测失败带来的转移损失。
据统计,转移指令大约占到了程序总指令数的 1/4,所以控制冒险会严重影响流水线的性能。为了解决控制冒险,可以采用下面的方法:
-
尽早判别转移是否发生,尽早生成转移目标地址。
-
预取转移成功和不成功两个控制流方向上的目标指令。
-
加快和提前形成条件码。
-
提高转移方向的猜测率。
三、流水线的性能指标
流水线的性能,一般用三项指标来衡量:吞吐率、加速比、效率。
1. 吞吐率
单位时间内流水线所完成的指令数,或者输出结果的数量,称为流水线的 吞吐率。吞吐率又有两种:
(1)最大吞吐率
最大吞吐率是指流水线在连续流动达到稳定状态后,所获得的吞吐率。对于 m 段的指令流水线,假设各段的时间均为 Δt,那么达到稳定状态后,每个 Δt 都会完成一条指令;所以最大吞吐量就是:
流水线只有在达到稳定、连续流动的时候,才能获得最大吞吐率。实际上,流水线在开始时有一段建立时间,结束时又有一段排空时间;另外还有各种冒险因素使流水线无法连续流动,所以实际吞吐率总是小于最大吞吐率。
(2)实际吞吐率
假设流水线中总共有 n 条指令,完成 n 条指令的总时间为 t,那么实际吞吐率就是 n / t。同样,对于 m 段指令流水线,若各段时间为 Δt,那么连续处理 n 条指令时,除了第 1 条指令需要 m · Δt 时间,其它 n - 1 条指令都是每隔 Δt 就会完成一条。所以:
这样,实际吞吐率的计算公式为:
带入最大吞吐率的公式,可以得到:
很明显,实际吞吐率 Tp < Tpmax,当 n → +∞ 时,Tp 趋近于 Tpmax。
2.加速比
流水线的加速比,指的是采用流水线处理指令的速度,和同样功能的非流水线的速度之比。
如果流水线每段时间均为 Δt,那么同样是完成 n 条指令,使用 m 段流水线需要的时间为:
而不使用流水线时需要时间为
则加速比 Sp 为:
可以看出,当 n → +∞ 时,Sp 趋近于 m,也就是流水线的最大加速比等于流水线的段数。
3.效率
流水线的效率,是指流水线中各功能段的利用率。
因为流水线有建立时间和排空时间,所以各功能段设备不会一直处于工作状态,会有一段空闲时间。假设 m 段流水线各段时间均为 Δt,我们可以将每个功能段(空间)在每个 Δt(时间)内的工作状态画出来,这就是流水线的 “时空图”:
上图是一个 4 段流水线(m = 4)的时空图,那么处理 n 条指令一共需要时间为 m · Δt + (n-1) · Δt,在时空图上总的时空区域为:m · (m+n-1) · Δt ;而各段真正处于工作的时空区域为:m · n · Δt。
一般就用流水线各段处于工作的时空区域,和总时空区域的比值,来衡量流水线的效率。计算公式为:
四、流水线的多发技术
流水线技术大大提升了 CPU 的处理效率,给计算机系统结构带来了重大改进。通过开发流水线的多发技术,又可以进一步地对流水线进行改进和提升。
所谓的多发技术,就是设法提升流水线的并行效果,尽量在一个时钟周期内可以完成更多的指令。常见的多发技术有 超标量技术、超流水线技术 和 超长指令字技术。
1.超标量技术
超标量(SuperScalar)流水线技术也叫做动态多发射技术,它是指在每个时钟周期内,可以同时并发多条独立的指令;也就是以并行操作的方式,将两条或两条以上的指令编译执行。
上图是普通流水线和超标量流水线的对比。这里是一条四级流水线,处理一条指令分为 4 个阶段:取指(IF)、译码(ID)、执行(EX)和 写回(WR)。假设每段时间就是一个时钟周期。
可以看到,普通流水线每个时钟可以产生一条指令的结果;而超标量流水线每个时钟周期都可以产生多条指令的结果。
要实现超标量技术,CPU 中要配置多个功能部件和指令译码电路,以及多个寄存器端口和总线,以便能够实现同时执行多个相同阶段的操作。另外,还需要编译器采用编译优化技术,找到能并行执行的指令进行调配。
2.超流水线技术
超流水线(SuperPipeline)技术是将一些流水线寄存器插入到流水线段中,相当于把每个阶段再进行细分。
上图中原来的一个时钟周期又分成了三段。这样超流水线的处理器可以在一个时钟周期内,让功能部件处理 3 条指令;相当于流水线是以 3 倍于原时钟频率的速度运行。
3.超长指令字技术
超长指令字技术(VLIW)也称静态多发射技术,它和超标量技术有共同特点,都是采用多条指令在多个部件中并行处理的架构,从而在一个时钟周期内能够流出多条指令。
跟超标量技术的区别在于:超标量的指令来自同一标准的指令流;而超长指令字则是由编译器挖掘出指令间潜在的并行性,然后把多条能并行的指令直接组合成一条指令——这是一条具有多个操作码字段的超长指令。这条超长指令可以控制机器中的多个独立的功能部件同时操作,相当于同时执行了多条指令。
超长指令字技术比超标量有更高的并行处理能力,但对编译器优化的要求更高,对 Cache 容量的要求更大。