2 1w
第 3 版 · OCR 辅助 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
计算机组成与系统结构(第3版)
186
预测位初始为0,根据一位预测位状态转换图可知,外循环中的分支指令只有最后一次
预测错误,其余都预测正确;而对于内循环中的分支指令,每次跳出内循环时预测位变为1,
再进入内循环时,第一次总是预测错误,并且任何一次循环的最后一次总是预测错误,因此,
总共有1+2×(N—1)次预测错误。
当N=10时,外循环中分支指令的预测正确率约为10/11×100?0.9???循环中
分支指令的预测正确率约为(110-19)/110×100?2.7??
当N=100时,外循环中分支指令的预测正确率约为100/101×100?9???循环
中分支指令的预测正确率约为(10100-199)/10100×100?8??
(2)两位预测位。
正确
发生
用两位组合成4种情况来表示预测和实际转移
不发生 错误
预测发生
预测发生
的状态,图6.23所示为两位预测位的状态转换图。
11
10
发生 正确
4个状态中,有两个状态预测发生转移,有两个
状态预测不发生转移。假定11状态表示预测发生
(强转移),实际不发生时,转到状态10(弱转移),下
次仍预测发生转移,如果再次预测错误(即实际不
发生),才使下次预测调整为00状态(强不转移)。
不发生,错误
发生错误不发生正确
预测不发生
预测不发生
01
00
发生 错误
(不发生》正确
图 6.23 两位预测位的状态转换
从图6.23 可看出,只有两次预测错误才改变预测
方向。
采用两位预测可避免一位预测时出现的一些问题,使得在连续两次发生不同的分支情
况时,也可能会预测正确。
例6.3 对于图6.22给出的程序,假定运行在一个采用两位预测位的流水线处理器上,
预测位初始为00。试分析当N=10和N=100时该程序段中各分支指令的预测正确率。
解:预测位初始为00,根据两位预测位状态转换图可知,外循环中的分支指令只有最后
一次预测错误,其余都预测正确;而对于内循环中的分支指令,每次跳出内循环时预测位变
为01,预测不发生,再进入内循环时,第一次分支指令实际上也不发生转移,预测正确,而且
预测状态变回00,因而又保证下次跳出内循环后再进入时,第一次总是预测正确,所以,对
于内循环总是只有最后一次预测错误,总共有N次预测错误。
当N=10时,外循环中分支指令的预测正确率约为10/11×100?0.9???循环中
分支指令的预测正确率约为(110-10)/110×100?0.9??
当N=100时,外循环中分支指令的预测正确率约为100/101×100?9???循环
中分支指令的预测正确率约为(10100-100)/10100×100?9??
由此可见,两位预测位方式下,内循环分支指令的预测正确率基本上与外循环的相当。
而一位预测位方式下,内循环中分支指令的预测正确率远不及外循环分支指令的预测正
确率。
目前,采用比较多的是两位预测位,也有的系统采用两位以上预测位,例如 Pentium 4
的 BTB2 采用了4位预测位。
注意,采用分支预测方式时,流水线控制逻辑必须确保错误预测指令的执行结果不能生
效,而且要能从正确的分支地址处重新启动流水线工作。
<!-- page: 204 -->
指令流水线
3)延迟分支
除了上述介绍的预测结合硬件阻塞和软件插入空指令的方法外,还可以采用延迟分支
(delayed branch)的方法来解决分支冒险。其主要思想是:采用编译优化来调整指令顺序,
把分支指令前与分支指令无关的指令调到分支指令后面执行,以填充延迟损失时间片,不够
时用 nop操作指令填充。分支指令后面被填的指令位置称为分支延迟槽(branch delay
slot),需要填入的指令条数(即分支延迟槽数)等于延迟损失时间片。
因为延迟分支技术通过编译器重排指令顺序来实现,所以它属于静态调度技术。图6.24
给出了一个分支延迟调度的例子。该例假定流水线的分支延迟损失时间片为2。从图6.24
可看出,在beq指令前的所有指令中,可以插到 beq指令后、add 指令前的只有第一条lw指
令和sub指令,但是,如果把这两条指令都调过去,则第2条lw指令和beq指令就会形成
load-use 数据冒险,因此,只能有一条指令填入分支延迟槽,另外还需再加一条nop指令。
1w
1w
sub
beq
add
Sw
§1, 0(52)
§3, 0(S2)
$6,S4,S2
$3,$5,2
$3,$3,52
$1,0($2)
⋯
调度后
1w
$3,0($2)
sub
§6,$4,§2
beq
§3,§5,2
1w
$1,0($2)
nop
add
$3,93,§2
S1.0(52)
sw
**⋯
图6.24 分支延迟调度的一个例子
对于分支冒险来说,分支延迟损失时间片是影响流水线执行效率的重要因素。分支延
迟损失时间片越小,插入的气泡或 nop指令越少,在预测错误时后退的指令条数越少,在分
支延迟方式下,调度到分支延迟槽的无关指令条数越少。例如,图6.24中假定分支延迟损
失时间片减少为1,则不需填入nop指令。
减少分支延迟损失时间片的关键是尽量提早进行分支条件的预测。例如,在图6.5所
示的流水线数据通路中,分支指令beq的条件检测在Mem 阶段进行,因而分支延迟损失时
间片为3。如果把检测操作往前调到Ex段,甚至提前到ID段,则可将分支延迟损失时间片
减少到 2,甚至减为1。
虽然延迟分支技术利用较小的软件代价解决了流水线阻塞问题,但是当采用一些高级
流水线技术和处理异常等情况时,延迟分支技术会存在以下一些问题。
(1)在高级流水线处理器中分支延迟槽数将成倍增加。例如,在超流水线处理器中,分
支延迟损失时间片将会更大;在多发射流水线处理器中,因为允许一个时钟周期处理多条指
令,所以分支延迟槽数也会成倍增加。假设某高性能处理器采用4-发射的10 级流水线架
构,其分支延迟损失时间片为8,若采用分支延迟调度技术,则需要往分支延迟槽填入32条
指令,才能完全消除分支延迟带来的开销。但一般情况下,编译器很难找到足够的指令来填
充分支延迟槽,只能往延迟槽中填充 nop指令,从而影响处理器的实际计算能力。有关超
流水线和多发射流水线的内容将在6.4节中介绍。
(2)分支预测错误的恢复变得复杂。若处理器中同时采用分支预测技术和延迟分支技
术,将会导致分支预测错误的恢复逻辑变得复杂。这是因为位于分支延迟槽中的指令并不
第6章
187
<!-- page: 205 -->
计算机组成与系统结构(第3版)
188
属于分支所在的路径上,无论分支指令最终是否发生转移,分支延迟槽中的指令都需要执
行,不能因为分支预测错误而被冲刷。为此,处理器中需要额外对分支延迟槽中的指令进行
标记。对于更复杂的高级流水线技术(如乱序执行),则需要引入更多的控制开销来从分支
预测错误中正确恢复。有关乱序执行技术将在6.4节中介绍。
(3)精确异常的实现开销增大,甚至难以实现。如果处理器在执行分支延迟槽指令的
过程中发生了异常,为了保证处理器将来能从发生异常的指令处重新开始执行,而且根据分
支指令的执行结果正确进行转移,那么处理器需要额外记录多种信息,并引入复杂的控制逻
辑。关于精确异常处理的概念将在“异常或中断引起的控制冒险”部分介绍。
(4)要求处理器的设计与具体的实现技术绑定。延迟分支技术需要软硬件协同工作,
是一个软件可见的技术,这样就会给处理器的具体实现带来无法摆脱的兼容性需求。具体
地,若某处理器的架构设计采用了延迟分支技术,则其后续产品的硬件实现都必须采用分支
延迟槽数相同的延迟分支技术,才能保证过去的软件可以在新产品上正确运行。这意味着
该处理器将永远无法摆脱上述延迟分支技术的缺点。
如前文所述,目前绝大多数处理器都采用动态预测技术。预测的正确率越高,分支延迟
损失时间片带来的性能影响就越小。例如,对于图6.5所示的流水线处理器中,其分支延迟
损失时间片为3,若为其配备一个预测正确率为95??动态分支预测器,则处理器执行一条
分支指令的平均延迟损失时间片只有3×(1-95?0.15。
MIPS处理器架构采用了延迟分支技术,其分支延迟槽数为1,因而所有 MIPS处理器
的实现都必须遵循这一约束,同时也需要引入各种开销来解决该技术带来的问题。例如,即
使设计一款 MIPS架构的单周期处理器,仍然需要考虑分支延迟槽的问题,才能正确运行各
种MIPS程序。针对延迟分支技术,GCC编译器提供了一个-fno-delayed-branch 的编译选
项,用于指示编译器往分支延迟槽中强制填充 nop指令。通过该选项编译出来的 MIPS程
序,即使在没有采用延迟分支技术的非标准MIPS处理器中也能正确执行,因为分支延迟槽
中放置的是nop指令,是否执行它并不影响程序的正确性;但对于标准的MIPS处理器,这
样的程序就完全没有发挥出延迟分支技术的优势,其效果等价于前文介绍的插入空操作指
令的消极方法。
鉴于上述缺点,RISC-V在设计分支指令的时候决定不采用延迟分支技术。
2.异常或中断引起的控制冒险
除了上述介绍的由于分支指令引起的控制冒险外,还有异常或中断引起的控制冒险。
异常和中断的出现会改变程序的执行流程,使得流水线执行发生阻塞。与分支冒险一
样,当某条指令执行过程中发现异常或中断时,可能它后面的多条指令已经被取到流水线中
正在执行。例如,ALU运算类指令发现“溢出”时,已经执行到 Ex阶段,此时,它后面已有
两条指令进入了流水线。
通过在数据通路的不同流水段中加入相应的检测逻辑可检测出哪条指令发生了异常。
例如,“溢出”可在Ex段检出;“无效指令”可在ID段检出;“除数为0”可在ID段检出;“无效
指令地址”可在IF段检出;“无效数据地址”可在 load/store指令的 Ex段或Mem段检出。
检测出异常的那个流水段正在执行的指令,就是发生异常的指令。
外部中断的检测可以放在第一个流水段IF或最后一个流水段Wr中进行。若放在IF
中检测,因为可在取指令前进行,若发现有中断请求发生,则能确保在该时钟周期就开始执
<!-- page: 206 -->
指令流水线
行中断服务程序,并让已经在流水线中的指令继续执行完,不需要进行指令冲刷;若放在
Wr阶段进行,则需要将刚执行完的指令后面几条指令从流水线中清除掉。
对于图6.5所示的5段流水线处理器,任何一个时钟周期中都有5条活动的指令,因而
很可能在一个时钟周期内同时有多条指令发生异常或中断,不同流水段发生不同类型的异
常。例如,在 Ex阶段add指令发生“溢出”的同时,ID阶段的指令发生了“无效指令”,Mem
阶段的lw指令发生了“缺页”,并且又发生了外设I/O中断请求。上述这种情况下,显然应
该先响应和处理lw指令的“缺页”异常。对于这种同时发生多个异常和中断的情况,最关键
的问题是要确定哪条指令的异常应最先被响应和处理。显然,排在前面的指令发生的异常
响应优先级高,因此,优先级确定原则是,在同一个时钟周期内的指令序列中,排在最前面的
指令所产生的异常最先被响应,外部中断请求最后响应。即对同时在5个指令流水段中发
生的异常进行排序时,其顺序为Wr>Mem>Ex>ID>IF。
处理器硬件对异常和中断引起的冒险的处理大致做法如下:当检测到有异常或中断
后,首先清除发生异常的指令以及其后在流水线中的所有指令,然后保存断点,并将异常处
理程序的首地址送 PC的输入端。指令清除的方式和上述分支预测错误时指令清除方式类
似,通常是通过相应的冲刷(flush)控制信号将指令或指令的控制信号清零(主要保证写信
号 RegWr和 MemWr清零)来实现。
许多处理器都能提供一种精确的异常和中断方式。所谓精确的异常和中断,是指处理
器能够确定异常和中断发生的精确位置,即处理器响应异常和中断时,所保存的断点是精确
的返回地址。
因为以流水线方式执行指令时,异常可能发生在不同的阶段,因而会产生一些潜在的危
险。例如,在图6.5所示的5段流水线处理器中,假定正在执行指令序列为“lw-add-ori⋯”,
而且lw指令在Mem段发生“缺页”,add指令在Ex段发生“溢出”,ori指令在IF段发生“指
令地址越界”。这种情况下,按正确的处理顺序,应该先处理lw指令的异常。但是,因为ori
指令处于IF段时,lw才处于Ex段,add才处于ID段,因此,此时ori前面的两条指令都还
没有发生异常。如果马上就处理ori指令的异常,则 add指令和 lw 指令的异常就会被忽
略,从而导致程序被错误执行。因此,通常的做法是,每个时钟周期内,在多个流水段发生的
异常的原因和断点只是被记录到特定的寄存器中,并将发生异常的标记同时记录到流水段
寄存器,发生异常的指令继续在流水线中执行,直到执行到最后一个阶段,由最后阶段内的
硬件检测本指令是否发生过异常或此时是否有外部中断发生,若有,则清除流水线中后面所
有阶段正在执行的指令,然后转到相应的异常处理程序执行。
及时检测到“异常”并处理是非常重要的,否则将会发生错误。例如,在执行指令“Iw
$1,0($1)”时,若没有及时捕获到“缺页”或没有及时冲刷RegWr等控制信号,则可能会
使$1改变其值,再重新执行该指令时,所读的内存单元地址可能被改变而发生严重错误。
至此已经讨论了单周期、多周期和流水线3种处理器实现方式,指令在这3种处理器上
采用不同的执行方式,得到不同的执行效率。从下面的例子中可以看出,虽然单周期和理想
流水线两种方式下的CPI都是1,但时钟周期的宽度相差很大,因而,同样的程序所花时间
相差很大。
例6.4 假设数据通路中各主要功能单元的操作时间为:存储器为400ps;ALU 和加法
器为200ps;寄存器堆读口或写口为100ps。假设MUX、控制单元、PC、扩展器和传输线路
第6章
189
<!-- page: 207 -->
计算机组成与系统结构(第3版)
190
等的延迟忽略不计,程序中指令的组成比例为:取数指令占25???数指令占10?LU
运算类指令占52???支指令占11???转指令占2??则下面的实现方式中哪个更快?
快多少?
(1)单周期方式(见图5.16),每条指令在一个固定长度的时钟周期内完成。
(2)多周期方式(见图5.25),每类指令时钟数为:取数-5,存数-4,ALU-4,分支一
3,跳转一3。
(3)流水线方式(见图6.5),每条指令分取指令、取数/译码、执行、存储器访问和写回5
个阶段。假定没有结构冒险;数据冒险采用“转发”技术处理;分支延迟损失时间片为1,预
测准确率为75???考虑异常、中断和访问缺失引起的流水线阻塞。
解:CPU执行时间=指令条数×CPI×时钟周期,对于同一个程序,3种方式的指令条
数都一样,因此只要比较 CPI和时钟周期即可。
根据已知条件,得到各类指令实际需要的执行时间如下。
取数指令:取指400ps+寄存器读100ps+ALU运算200ps+取数400ps+寄存器写
100ps=1.2ns
存数指令:取指400ps+寄存器读100ps+ALU运算200ps+存数400ps=1.1ns。
ALU指令:取指 400ps+寄存器读100ps+ALU 运算 200ps+寄存器写100ps
=800ps
分支指令:取指400ps+寄存器读100ps+ALU 运算200ps=700ps。
跳转指令:取指 400ps。
(1)单周期方式下,时钟周期由最长的取数指令确定,为1.2ns。因此,N条指令的执行
时间为1.2Nns。
(2)多周期方式下,以功能部件最长所需时间作为时钟周期,因为存储器访问操作时间
最长,为400ps,所以,时钟周期为400ps。根据各类指令的频度,计算出平均时钟周期数为
5×25?×10?×52?×11?×2?.12
所以,N条指令的执行时间为4.12×400ps×N=1.648Nns。
(3)流水线方式下,流水线的时钟周期取功能部件最长所需时间为400ps。每类指令所
需的时钟数如下。
取数指令:当发生load-use冒险时,执行时间为2个时钟周期,否则为1个时钟周期,
故平均执行时间为1.5个时钟周期。
存数指令、ALU指令:因为采用了“转发”机制,所以流水线不会被阻塞,故只需1个时
钟周期。
分支指令:分支延迟损失时间片为1,因而预测错误时阻塞1个时钟周期。这样,预测
成功时需1个时钟周期,预测错误时需2个时钟周期。平均约为0.75×1+0.25×2=1.25
个时钟周期。
跳转指令:需等到译码阶段结束才能得到转移地址,故需2个时钟周期。
因此,平均CPI为1.5×25?×10?×52?.25×11?×2?.17。
所以,N条指令的执行时间为1.17×400ps×N=0.468Nns。
综上所述,流水线方式的执行速度最快,与单周期相比,约为1.2ns/0.468ns=2.56倍;
与多周期相比,约为1.648ns/0.468ns=3.52倍,都要快一倍以上。
<!-- page: 208 -->
指令流水线
从上例可以看出,单周期比多周期快,而流水线是单周期的两倍多,这主要是因为访存
操作消耗了特别长的时间,使得时钟周期较大,访存过程是多周期和流水线方式共同的瓶
颈。若将访存分解为两个时钟周期,可以使时钟周期降为200ps,这对多周期和流水线两种
方式都会带来性能上的改进。
#### 6.4 节介绍的超流水线技术就采用了将流水段更细、更均匀地划分的思想。
6.4高级流水线技术
高级流水线技术充分利用指令级并行(instruction level parallelism,ILP)来提高流水线
的性能。有两种增加指令级并行的策略。
一种是超流水线(super-pipelining)技术,通过增加流水线级数来使更多的指令同时在
流水线中重叠执行。超流水线并没有改变CPI的值,CPI还是1,但是,因为理想情况下流
水线的加速比与流水段的数目成正比,所以,流水段越多,时钟周期越短,指令吞吐率越高。
因此,超流水线的性能比普通流水线好。但是,流水线级数越多,用于流水段寄存器的开销
就越大,因而流水线级数是有限制的,不可能无限增加。
另一种是多发射流水线(multiple issue pipelining)技术,通过同时启动多条指令(如整
数运算、浮点运算、存储器访问等)独立运行来提高指令并行性。要实现多发射流水线,其前
提是数据通路中有多个执行部件,如定点、浮点、乘/除、取数/存数部件等。多发射流水线的
CPI能达到小于1,因此,有时用CPI的倒数IPC来衡量其性能。IPC(instructions per
cycle)是指每个时钟周期内完成的指令条数。例如,四路多发射流水线的理想IPC为4。
实现多发射流水线必须完成两个任务:指令打包和冒险处理。指令打包任务就是将能
够并行处理的多条指令同时发送到发射槽中,因此处理器必须知道每个周期能发射几条指
令,哪些指令可以同时发射。这通过推测(speculation)技术来完成,可以由编译器或处理器
通过猜测指令执行结果来调整指令执行顺序,使指令的执行能达到最大可能的并行。指令
打包的决策依赖于“推测”的结果,主要根据指令间的相关性来进行推测,与前面指令不相关
的指令可以提前执行。例如,如果可以推测出一条load指令和它之前的store指令引用的
不是同一个存储地址,则可以将load指令提前到store指令之前执行;也可对分支指令进行
推测以提前执行分支目标处的指令。不过,推测仅是“猜测”,有可能推测错误,故需有推测
错误检测和回退机制,在检测到推测错误时,能回退掉被错误执行的指令。因此,错误推测
会导致额外开销。需要结合软件推测和硬件推测来进行,软件推测指编译器通过推测来静
态重排指令,此种推测一定要正确;而硬件推测指处理器在程序执行过程中通过推测来动态
调度指令。
根据推测打包任务主要由编译器静态完成还是由处理器动态执行,可将多发射技术分
为两类:静态多发射和动态多发射。
*6.4.1静态多发射处理器
静态多发射处理器主要通过编译器静态推测来辅助完成“指令打包”和“冒险处理”。指
令打包的结果可看成将同时发射的多条指令合并到一个长指令中。通常将一个周期内发射
的多个指令看成一条多个操作的长指令,称为一个发射包。静态多发射指令最初被称为超
第6章
191
<!-- page: 209 -->
计算机组成与系统结构(第3版)
192
长指令字(very long instruction word,VLIW),采用这种技术的处理器被称为VLIW处理
器。Intel公司的IA-64架构采用这种方法,并称其为 EPIC(explicitly parallel instruction
computer,显式并行指令计算机)。
因为数据通路中功能部件及其个数是确定的,所以,同一时钟周期内发射的指令类型和
指令个数是受限的。例如,如果ALU运算部件和存储访问部件独立设置,并只各提供一
套,则只能同时发射一条ALU指令/分支指令和一条load/store指令。
静态多发射处理器的冒险处理主要是数据冒险和控制冒险。处理冒险的方式可有两
种:一种是完全由编译器通过代码调度和插入nop指令来静态地消除所有冒险,无须硬件
实现冒险检测和流水线阻塞;另一种是由编译器通过静态分支预测和代码调度来消除打包
指令的内部依赖,由硬件检测数据冒险并进行流水线阻塞。
为了使读者对静态多发射处理器有一定的感性认识,以下以一个简单的2-发射 MIPS
处理器为例来分析静态多发射处理器的基本实现,然后简要介绍Intel公司的IA-64的结构
特点。
1.2-发射 MIPS 处理器
要使原来的 MIPS处理器能够同时处理两条流水线,数据通路必须进行相应的改进。
(1)因为需要同时读取并译码两条指令,因而,可将两条指令打包成64位长指令,前面
为ALU/beq指令,后面为load/store指令,没有配对指令时,就用nop指令代替,将64位长
指令中的两个操作码同时送到控制器(指令译码器)进行译码。
(2)因为两条指令可能同时读两个寄存器(和store配对时)或同时写两个寄存器(和
load 配对时),所以需要增加一个寄存器读口和一个写口。
(3)因为在上一条 ALU/beq指令进行ALU运算时,本条load/store指令要计算地址,
所以需要增加一个加法器或ALU 运算部件(包括两组输入总线和一组输出总线)。
(4)流水段寄存器要增宽,因为两条指令的数据信息和控制信号在流水段寄存器中被
分别传送。
2-发射处理器的潜在性能将提高大约两倍,但由于各种原因,实际上达不到。静态多发
射处理器的缺点是,为消除结构冒险需增加额外部件。此外,由于多条流水线同时发射执
行,使得一旦发生数据冒险或控制冒险便会有更多的指令被阻塞在流水线中,因而增加了潜
在的性能损失。例如,对于load-use数据冒险,在单发射流水线下,只有一条指令被延迟执
行,而在2-发射流水线下,因为一个时钟周期有两条指令在执行,所以,有两条指令被延迟
执行;又如,对于ALU-load/store数据冒险(一条 ALU指令后跟一条load或 store指令),
在单发射流水线下,可用“转发”技术使ALU结果直接转发到load/store指令的Ex阶段,
但在2-发射流水线下,因为两条指令同时进行,所以,ALU的结果不能直接转发,只能延迟
一个时钟周期再执行load/store指令。为了更有效地利用多发射处理器的并行性,必须有
更强大的编译器,能够充分消除指令间的依赖关系,使指令序列达到最大的并行性。
以下用一个例子来说明编译器如何进行静态指令调度。假设有一个程序段用来实现对
一个数组中的所有元素依次进行加1操作,该程序段在 MIPS机器上对应的机器代码段
如下:
loop: 1w
$t0, 0($s1)
#从存储单元取数,送st0
addiu st0, St0, 1
#§t0增量
<!-- page: 210 -->
指令流水线
st0, 0($s1)
#StO 送回存储单元
sw
addi
5s1,Ss1,-4
#存储单元地址减4
bne
Ss1,S zero,loop
#若存储单元地址不为0,则继续循环
由于受2-发射MIPS流水线处理器结构的限制,指令被分成两类。一类是ALU/分支
指令;另一类是lw/sw指令。为了能在2-发射MIPS流水线中有效执行上述程序,需重新
排列指令序列。前3条指令之间和后2条指令之间各有相关性,因此,可把第4条指令调到
第1条后面,但因为$s1先被减4,故sw指令的偏移应改为4。调度后得到的2-发射流水
线指令序列如图 6.25所示。
ALU/分支指令
load/store 指令
时钟周期
loop:
nop
lw $to,o($sl)
1
addi
$sl,$s1,-4
nop
2
addiu $t0,$t0,1
nop
3
bne
Ss1,$zero,loop
sw $t0,4($sl)
4
图 6.25 2-发射 MIPS 流水线的指令代码调度例子
上述调度结果是循环体内5条指令在4个时钟周期内完成,因此,实际CPI为0.8,即
IPC=1.25。这个结果与理想情况相比,相差很大。对于在循环结构中的代码,更好的调度
技术是“循环展开”。
循环展开的基本思想是,将循环体展开生成多个副本,在展开的指令中统筹调度。例
如,对于上例,若循环执行次数是4的倍数,则可循环展开4次,其最佳调度序列如图6.26
所示。
ALU/分支指令
load/store 指令
时钟周期
loop:
addi
$s1,$sl,-16
lw
$to,0($s1)
1
nop
lw
$t1,12($sl)
2
addiu
$t0,$t0,1
lw
$t2,8($sl)
3
addiu
$t1,$t1,1
lw
$t3,4($sl)
4
addiu
$t2,$t1,1
sw
$t0,16($sl)
5
addiu
St3,$t3,1
sw
$t1,12($sl)
6
nop
sw
$t2,8($s1)
7
bne
Ss1,S zero,loop
sw
$t3,4($sl)
8
图 6.26 2-发射 MIPS 流水线的指令代码循环展开调度例子
循环展开4次后,每次循环体内对4个数组元素进行操作,数组首地址在$s1中。因
为addi指令先将$s1减16。因此,如图6.27所示,循环体内被操作的4个数组元素的地址
分别是($s1)+16、($s1)+12、($s1)+8、($sl)+4。因为第1个时钟周期的lw指令进
行地址计算时,addi指令的执行结果还没写到$s1中,此时$sl还是原来的值,因此该lw
第6章
193
<!-- page: 211 -->
计算机组成与系统结构(第3版)
194
指令的地址偏移是0而不是16。
低地址
循环展开4次后,循环内与数组元素的访问和操作相
关的指令(lw、addiu、sw)各有4条,再加上1条 addi 和1
条 bne,共14条指令,用了8个时钟,CPI达到8/14=
数组元素4
数组
数组元素3
访问
数组元素2
方向
0.57,比未进行循环展开好。
数组元素1
Ss1
高地址
在循环展开过程中,用到了“重命名寄存器”技术,多
用了3个临时寄存器$t1、$t2、$t3来消除名字依赖关
系。因为名字依赖是一种非真实依赖,只是寄存器名相同
而已,实际上并不是同一个寄存器,因此可以用另一个寄存器名替换。
图6.27 循环展开后的数组元素
在循环展开时,需要注意尽量不能引起新的数据冒险。例如,第1条addiu指令不能放
在第2时钟周期,否则会引起load-use数据冒险。
从上述例子可以看出,循环展开确实能提高程序执行效率,但是,这也是有代价的。本
例的代价是多用了3个临时寄存器,并增加了程序的代码长度,因为循环体变长使存储空间
变大了。
当然,如果上述例子中循环次数不是4的倍数,那么就会有问题。这种情况下,除了调
整循环次数外,还要对多出来的不足4次的循环另外进行处理。
2.Intel IA-64 的结构特点
20多年来,IntelIA-32体系结构一直是市场上最流行的通用处理器架构。1985年推出
的 Intel 80386处理器是IA-32家族中的第一款产品,它是典型的CISC风格指令集体系结
构,而随后的1A-32处理器都与它保持向后兼容,因此,从整体上来看,IA-32 仍是基于
CISC架构的体系结构。但从1993年推出的奔腾(Pentium)处理器开始,RISC设计思想逐
渐被引入,Intel公司最终将 Pentium4处理器设计成了被称为“CISC壳、RISC 核”架构的
处理器,CISC指令在执行时被转换成一条或多条类似RISC指令的微操作。有关Pentium
4的具体内容详见6.4.3节。
随着计算机技术及应用领域的不断发展,32位处理器逐步开始向64位处理器过渡。
Intel公司最早推出的64位架构是基于超长指令字技术的安腾(Itanium)和安腾2(Itanium 2)
处理器,分别在2000年和2002年问世,它们是IA-64体系结构最早的具体实现。安腾体系
结构完全脱离了IA-32 CISC 架构的束缚,采用全新的 EPIC技术。
IA-64体系结构采用类似64位MIPS架构的RR型 RISC风格指令集,但与 MIPS 64
体系结构有一些差别,它主要有以下几方面的特点。
(1)具有比 MIPS64更多的寄存器,包含128个整数、128个浮点数、8个专用分支、64
个1位谓词寄存器。支持寄存器窗口重叠技术,提供一组窗口寄存器,在执行过程调用和返
回时,利用窗口寄存器来完成参数传递。
(2)要求编译器显式地给出指令级的并行性,并行执行信息被明显标记在代码中。指
令组是指相互间没有寄存器级数据依赖的指令序列,用“停止标记”在指令组之间明显标识,
指令组内部的所有指令可并行执行,只要有足够硬件且无内存操作依赖。
(3)程序执行时将同时发射的指令重新编码并组织在一个指令包(bundle)中,每个指
令包的长度为128位,由5位长的模板字段和3个41位长的指令组成。
(4)引入特殊的谓词化技术,以支持推测执行和消除分支,提高指令级并行度。“谓词”
<!-- page: 212 -->
指令流水线
是指分支指令中的条件,每个谓词与一个谓词寄存器相关联。指令最后6位是一个标识谓
词寄存器的编号,因此,每条指令用该编号与一个谓词寄存器关联,以反映条件是否满足。
这类似于ARM架构中的条件字段。
IA-64体系结构试图完全脱离IA-32 CISC 架构的束缚,最大限度地提高软件和硬件之
间的协同性,力求将处理器的处理能力和编译程序的功能结合起来,在指令中将并行执行信
息以明显的方式告诉硬件。但是,这种思路被证明是不易实现的,而且,因为采用了全新的
指令集,虽然可以在兼容模式中执行IA-32代码,但是性能不太好,因而安腾并没有在市场
上获得预想的成功。
AMD公司利用Intel公司在IA-64架构上的失败,抢先在2003年推出了兼容IA-32的
64位版本指令集x86-64,它在保留IA-32指令集的基础上,增加了新的数据格式及其操作
指令,寄存器长度扩展为64位,并将通用寄存器个数从8个扩展到16个。通过x86-64,
AMD公司获得了以前属于Intel公司的一些高端市场。AMD公司后来将 x86-64更名
为AMD64。
Intel公司发现用IA-64直接替换IA-32行不通,于是,在2004年推出了IA32-EM64T
(extended memory 64 technology,64位内存扩展技术),它支持x86-64指令集。Intel公司
为了表示 EM64T的64位模式特点,又使其与IA-64有所区别,2006年开始把 EM64T 改
名为Intel64。因此,Intel 64是与IA-64完全不同的体系结构,它与 IA-32 和 AMD64
兼容。
以上简要介绍了静态多发射流水线技术,可以看出,计算机的体系结构和编译器的关系
非常紧密,编译器的好坏直接影响程序的性能。实现编译器的程序员必须对机器结构非常
了解,才能开发出质量优良的编译器。
*6.4.2动态多发射处理器
动态多发射流水线处理器在指令执行时由处理器硬件动态进行流水线调度来完成指令
打包和冒险处理,能在一个时钟周期内执行一条以上指令。
在动态多发射处理器上要达到较好的性能,也需要由编译器进行静态调度,以尽量消除
依赖关系,使其达到较高的发射速率。但这种静态调度和静态多发射处理器的静态调度有
些不同。对于静态多发射处理器的静态调度来说,编译结果与机器结构密切相关;而对于动
态调度来说,由于完全由硬件决定某个时钟周期发射哪几条指令,因而编译器仅进行指令顺
序调整,而不需要根据机器结构进行指令打包。
动态多发射处理器也称为超标量(superscalar)处理器。在简单的超标量处理器中,指
令按顺序发射,每个周期由处理器决定是发射一条还是多条指令。显然,在这种处理器上要
达到较好的性能,很大程度上依赖于编译器。为了更好地发挥超标量处理器的性能,多数超
标量处理器都结合动态流水线调度(dynamic pipeline scheduling)技术,处理器通过指令相
关性检测和动态分支预测等手段,投机性地不按指令顺序执行,当发生流水线阻塞时,根据
指令的依赖关系,动态地到后面找一些没有依赖关系的指令提前执行。这种指令执行方式
称为乱序执行(out-of-order execution)。
例如,对于以下一段 MIPS指令序列:
第6章
195
<!-- page: 213 -->
计算机组成与系统结构(第3版)
196
1w
$t0, 0(Ss1)
add
St2,$t0,st1
sub
s2,§s2,$t3
addiu $t4,Ss2,20
第1条和第2条指令存在load-use数据冒险,而且lw指令本身耗时较长,容易发生访
问不命中引起的流水线阻塞,因此可以采用动态流水线调度方式,将sub指令调到第二条指
令前面提前执行,不需等lw和add指令执行完。addiu指令也可提前,但因为和sub指令有
依赖关系,所以要保证它在sub指令后执行。
为了实现动态多发射和动态流水线调度,处理器中需要提供一些必要的机制和相应的
处理部件,如指令预取部件、指令调度与分派部件、多个功能部件、重排序缓存等。图6.28
是动态多发射流水线处理器的通用模型示意图。
指令预取/
译码单元
指令
cache
指令队列
第n条
第 n-1条
指令分派器
寄存器堆
转发
重排序缓冲
整数部件
指令总线
操作数总线
浮点部件
保留站
数据
存/取部件
cache
操作结果总线
图6.28 动态多发射流水线处理器的通用模型
如图6.28所示,动态多发射流水线处理器主要由以下几部分组成。
(1)指令预取和译码单元。为了保证流水线中有足够的指令执行,必须要有指令预取
功能。预取的指令经译码后,放到一个指令队列中。
(2)指令分派(dispatch)器。通过分析指令功能和指令间的依赖关系,并根据功能部件
的空闲情况,确定何时发射哪条指令到哪个功能单元中。
(3)功能单元。超标量处理器中一定有多个功能单元,它们各自完成独立的操作,如整
数部件(整数加减、整数乘、整数除)、浮点部件(浮点加减、浮点乘、浮点除)、存/取部件等。
每个功能单元都具有一定的操作性能,通常用两个周期数来刻画。一个是执行周期数
(latency),表示完成特定操作所花的时钟周期数;另一个是发射时间(issue time),表示连
续、独立的两次操作之间的最短周期数。表6.1是Pentium Ⅲ的部分功能部件性能列表。
从表6.1可看出,整数加减、整数乘、浮点加减、取数、存数这5种部件是流水化的,浮点数乘
部件是部分流水化的,而整数除和浮点数除是完全没有流水化的。每个功能部件有各自的
缓冲器,称为保留站,用于保存操作数和操作命令。