第6章 利用流水线提高性能文档格式.docx
《第6章 利用流水线提高性能文档格式.docx》由会员分享,可在线阅读,更多相关《第6章 利用流水线提高性能文档格式.docx(56页珍藏版)》请在冰豆网上搜索。
![第6章 利用流水线提高性能文档格式.docx](https://file1.bdocx.com/fileroot1/2023-1/2/565ba738-c26c-4ab4-9b6e-117aba4316a0/565ba738-c26c-4ab4-9b6e-117aba4316a01.gif)
在图6.1中,流水线方式只将处理速度提高了2.3倍的原因是图中只显示了清洗四批衣服的处理过程。
图6.1以洗衣店为例来类比流水线的运作过程。
安妮,布朗,凯西和唐每个人都有一些脏衣服要清洗、脱水、折叠及储存。
洗衣机、脱水机、折叠机和储存机每个都需要三十分钟来完成各自的任务。
顺序的洗衣将花费8个小时的时间洗完四批衣服,而流水线的洗涤方法只需要花费3.5小时。
图中采用在这个二维时间轴上显示4道工作的副本的方法来表示同一时间不同的处理步骤对应的流水线步骤,而事实上我们每种洗衣设备都只有一台。
同样的原理也可以应用到处理器中,即采用流水线方式执行处理器指令。
通常,一个MIPS指令包含如下五个处理步骤:
1.从内存中读取指令。
2.指令解码的同时读取寄存器(MIPS的指令格式允许同时进行指令解码和读寄存器)。
3.执行操作或计算地址。
4.在数据内存中读取操作数。
5.将结果写回寄存器。
因此,本章讨论的MIPS流水线具有五个处理步骤。
正如流水线能加速洗衣店的工作一样,下面的例子将说明流水线如何加快指令的总体执行时间。
单周期指令模型与流水线性能
例题:
为了使问题具体化,我们首先假设一个流水线结构。
在本例以及本章所剩余的部分内容中,我们将只考虑以下的八条指令:
loadword(lw);
storeword(sw);
add(add);
subtract(sub);
and(and);
or(or);
set-less-than(slt)和branch-on-equal(beq)。
本实例将比较流水线指令执行与单周期指令执行的平均执行时间,其中在单周期模型中所有指令的执行都花费一个时钟周期。
本例中,主要功能单元的操作时间为内存访问:
2ns;
ALU操作:
寄存器文件的读与写:
1ns。
(在第五章中我们已经说明在单周期模型中每一条指令都恰恰只花费一个时钟周期,因此,时钟的周期必须延长以满足最慢的指令。
)
解:
八条指令中每一条指令所需要的执行时间如图6.2所示。
单周期模型的设计必须考虑到最慢的指令,在图6.2中是lw,因此,每一条指令所需要的执行时间为8ns。
与图6.1类似,图6.3比较了三条loadword指令非流水线与流水线的方式的执行过程,其中在非流水线模型中,第一条与第四条指令之间时间的差距是3x8=24ns。
指令类别
指令预取
寄存器读取
ALU操作
数据访问
寄存器写
总的执行时间
Loadword(lw)
2ns
1ns
8ns
Storeword(lw)
7ns
R-format
(add,sub,and,or,slt)
6ns
Branch(beq)
5ns
图6.2由各操作组件计算出来的八条指令总的执行时间。
假设乘法器、控制单元、PC访问和信号传递都没有延时。
图6.3单循环、非流水线的指令执行过程(上图)与流水线的指令执行过程(下图)。
两者采用相同的硬件组件,各组件的处理时间如图6.2。
在此种情况下,指令的执行速度提高了4倍,即从8ns降到了2ns。
将本图与图6.1比较。
在洗衣服的例子中,我们假设所有的处理过程的完成时间都是相等的。
如果脱水机运行得最慢,那么就把抽水的时间设定为各步骤需要的处理时间。
计算机的流水线过程处理时间也是受限于最慢的处理资源,即ALU操作和内存访问。
同时我们假设对寄存器文件的写操作发生在时钟周期的前半段,对寄存器文件的读操作发生在时钟周期的后半段,并且本章所有内容都遵循这个假设。
所有的流水线步骤都只花费一个时钟周期的时间,所以,时钟周期必须能够满足最慢的操作的执行需要。
这就象在单周期模型中虽然有些快的指令的执行只需要5ns,但它必须选择在最坏情况下的8ns做为时钟周期一样,流水线执行模型的时钟周期也必须选择最坏情况下的2ns而不是有些步骤可以达到的1ns。
流水线能够将性能提高四倍:
第一与第四条指令之间的时间差距缩短为3x2=6ns。
我们可以把上面讨论的流水线模型能够获得的性能加速比归纳成一个公式。
如果流水线各阶段操作平衡,那么在流水线机器上指令执行时间为(在理想情况下):
指令执行时间(流水线)=指令执行时间(非流水线)/流水线步骤数
即在理想的情况下,流水线所带来的加速比与流水线的执行步骤的数目相同。
一个有五个执行步骤的流水线能获得加速比也是五。
这个公式说明一个有五个步骤的流水线在8ns的非流水线执行时间或者1.6ns的时钟周期的基础上获得获得五倍的速度提高。
然而,在例子中显示,各个步骤间并不是完全的平衡的。
另外,流水线中还包括一些常规的额外开销。
所以,在流水线机器中每一条指令的执行时间会超过这个最小的可能值,因此流水线能够获得加速比也就小于流水线的步骤数。
此外,即使我们在前面的分析中断言能将指令的执行速度提高四倍,但本例子三条指令的执行中并没有反映出来,它实际获得的加速比为24ns/14ns。
为什么实际的加速比小了许多呢?
如果增加执行指令的数目将会发生什么呢?
我们首先将前面的图中的指令增加到1003条,也就是说再在上面的流水线例子中加入一千条指令,每一条指令都将会使整个的执行时间增加2ns,因此,整个的执行时间就变成1000x2ns+14ns,即2014ns。
在非流水线的例子中,我们也加入1000条指令,每条指令的执行时间是8ns,因此整个的执行时间为1000x8ns+24ns,即8024ns。
在这种理想的条件下,非流水线程序与流水线程序的实际执行时间的比值就非常接近与两者指令执行时间的比值,即为:
8024ns/2014ns=3.98≈8ns/2ns
流水线所带来的性能的提高是通过增加指令的吞吐率来实现的,而不是减小单条指令的执行实现的。
由于实际程序都会执行成千上万条指令,因此,指令的吞吐率是一个很重要的参数。
设计流水线指令集
尽管上面的例子只是对流水线的最简单的说明,我们也能够通过它讨论如何设计MIPS指令集,即如何设计能以流水线方式执行的指令集。
首先,所有的MIPS指令的长度都必须相同。
这一限制将简化流水线的第一步预取指令与第二步指令解码。
在诸如80x86之类的系统的指令集中,指令的长度并不相同,从1byte到17byte不等,这样将会给流水线的执行带来更大的挑战性。
第二,MIPS只有很少的几种指令格式,并且每一条指令中的源寄存器的位置都是相同的。
这种对称性意味着流水线的第二个步骤在硬件确定提取的指令类型的同时就能够开始读寄存器文件。
如果MIPS的指令格式是非对称的,我们就需要将第二个步骤一分为二,从而使得流水线的步骤数变为六(我们不久将看到较长的流水线的缺点)。
第三,MIPS中的内存操作数仅出现在load和store操作中。
这一限制意味着可以利用执行步骤计算内存地址,就可以接着在下一个步骤访问内存。
如果我们可以在内存中操作操作数,就像在80x86中那样,那么步骤3与步骤4将会扩展为地址步骤,内存步骤,和执行步骤三步。
第四,所有操作数必须在内存中排成一列(详情请查阅第三章软硬件界面一节相关内容)。
因此,我们不需要担心一个数据传输指令需要两次内存访问的情况,所请求的数据可以在一个的流水线步骤完成在处理器与内存之间传输。
流水线冒险(pipelinehazard)
流水线有这样一种情况,在下一个时钟周期中下一条指令不能执行。
这种情况称为流水线冒险。
我们将介绍三种流水线冒险。
介绍每种流水线冒险时我们都首先利用前面关于洗衣过程的类比作一简单的描述,然后给出计算机上流水线存在的相应问题及其解决方法。
结构冒险
第一种冒险叫做结构冒险。
即硬件不支持期望的多条指令在同一个时钟周期执行。
在洗衣店例子中,如果用洗衣-脱水机代替独立的洗衣机与脱水机,或者如果我的室友正在做其它的事情而不能帮助我将衣服收拾好,都会发生结构冒险。
如果发生上述情况,那我们精心构筑起来的流水线就会被破坏。
正如我们在上面所说的那样,MIPS的指令集是为流水线设计的。
因此,它就要求设计者能够非常容易的在设计流水线时避免结构冒险。
假设图6.3的流水线结构只有一个内存而不是两个内存,那么如果有第四个指令的话,我们将会发现,在某一个时钟周期,第一条指令在访问内存的同时第四条指令将会在同一内存中预取指令。
如果没有两个内存的话,流水线就会发生结构冒险。
控制冒险
第二种冒险叫做控制冒险。
这种冒险会在下面的情况下出现:
决策依赖于一条指令的结果,而正好其他的指令在执行中。
假设洗衣店的店员们接到了一个令人高兴的任务:
为一个足球队清洗队服。
由于衣服非常的脏,我们需要确定清洗剂的用量以及水的温度设置是否能够将衣服清洗干净,但同时要保证不是过大以避免过度的磨损衣物。
在洗衣店流水线中,店员只有等到第二步甩干衣服以后才能确定是否需要改变洗衣机的设置。
在这种情况应该怎么办呢?
有两种办法可以解决洗衣店的控制冒险,同样的方法也可以应用到计算机中。
阻塞:
在第一批被甩干之前按串行的方式操作,并且重复这一过程直到找到正确的洗衣机设置为止。
这种保守的方法当然可以保证正常工作,但它的速度比较慢。
计算机中的决策就是分支指令。
如果计算机在一个分支前被阻塞,那么流水线就不得不暂停。
假设我们可以加入足够多的硬件使得能在流水线的第二阶段测试寄存器、计算分支地址并更新PC(详情参见6.6)。
通过这些额外的硬件,包含的条件分支的流水线执行情况如图6.4所示。
图中如果分支失败,指令lw在开始执行之前就要被阻塞了额外的2ns的时钟周期。
图6.4说明了一个重要的流水线概念:
正式的名称叫做流水线阻塞,但是它经常被亲昵的叫做气泡(bubble)。
我们经常会在流水线中看到阻塞的发生。
阻塞对分支性能的影响
例题:
评价分支阻塞对单位指令时钟周期数(CPI)的影响。
假设其它所有指令的CPI都为1。
解:
第三章的图3.38显示条件分支占gcc执行指令的17%。
由于其它指令的CPI都为1,而分支指令要多一个时钟周期被阻塞,因此平均CPI为1.17。
与理想的情况相比,现在的速度下降到了1.17倍(由于图3.38还包括了分支指令slt与slti,而它们并不会阻塞,因此上述CPI只是一个近似值)。
图6.4在每一个条件分支上阻塞是避免流水线控制危险的一种解决方法。
本图中在分支后有一步流水线阻塞(或者叫气泡)。
如果不能在第二步中解决分支问题(这种情况在较长的流水线中经常发生),那么在分支结构上的阻塞将导致更大的速度下降。
对很多的计算机来说,这种阻塞的方法应用代价太大。
因此也就产生了另外一种消除控制冒险的方法:
预测:
如果你确信能正确的设置洗衣设备来洗涤那些队服的话,即你可以预测它的工作,那么就可以在第一批衣服甩干的同时清洗第二批衣服。
这种做法在预测正确的时候不会降低流水线的速度,但是一旦预测错误,就不得不将已经洗过的队服重新洗一遍。
计算机的确是采用预测的方法来处理分支。
一种简单的预测方法就是总预测分支会失败。
当你预测正确(即分支失败)的时候,流水线会全速的进行处理。
只有当分支成立时流水线才会阻塞。
图6.5给出了这样一个例子。
图6.5预测分支不会执行是一种避免流水线控制冒险的一种解决方法。
上图显示的是分支没有执行的流水线,下图显示的是分支发生了的流水线。
一种更加成熟的分支预测方法是预测一些分支发生而预测另外一些分支不发生。
如在上面的洗衣店的例子中,夜晚和主场比赛的对服使用一个洗衣设备设置而白天或客场比赛的对服则使用另一个设置。
举一个计算机的例子,在循环体的底部分支总是会跳回到循环体的顶部。
由于分支总是被执行并且总是向前跳转,因此我们可以预测分支会跳转到前面的某一地址上。
这种分支预测的方法依赖于一成不变的行为,它没有考虑特定的分支指令的特点。
动态硬件预测器与这种方法截然不同,它的预测依赖于每一条指令的行为,并且在整个的程序生命期内可能改变分支的预测。
与洗衣店的例子类比,使用动态预测方法,店员将会观察制服的肮脏程度然后在假设一个洗衣设备设置,然后在本次预测的成功的基础上调整下一次的预测行为。
计算机中动态预测方法的一种比较普遍的实现方法是保存每次分支的历史纪录,然后利用这个历史纪录来预测未来。
这种硬件的方式能够达到90%的正确性(参阅6.6)。
当预测错误时,流水线控制必须确保被错误预测了的分支后面的指令不会生效并且必须在正确的分支地址处重新开始启动流水线。
如同其它解决控制冒险的方法一样,较长的流水线会恶化预测的性能,它将提高解决错误预测的代价。
控制冒险的解决办法在6.6节中将有更加详细的介绍。
细节:
还有一种解决控制冒险的方法,即延迟决定(delayeddecision)。
与洗衣店的例子类比,每当要决定如何洗衣服时,就将一批非足球队的衣服放进洗衣机里,同时等待足球队的制服被甩干。
只要有足够多不需要决策的脏衣服,这种方法就能够很好的工作。
在计算机中这种方法被称为延迟分支,在MIPS系统结构中也得到了实际应用。
延迟分支发生分支的下一个顺序的指令,而在一个指令的延迟之后再开始执行分支。
由于编译器会自动的排列指令使得分支的行为达到程序员的要求,因此这个过程对MIPS的汇编程序员们是透明的。
MIPS软件会在延迟分支指令的后面紧跟着放一条不受该分支影响的指令。
发生了的分支会改变这条安全指令之后的指令的地址。
在我们的例子中,图6.4中分支前的指令add不影响分支,所以在图6.6中就把它移到了分支之后的延迟分支时间片中。
编译器一般只能在大约50%的分支延迟时间片中放入有用的指令。
如果流水线比五个步骤长的话,将会有更多的延迟分支时间片,这些时间片将更加难以填满。
图6.6推迟分支是避免流水线控制冒险的一种解决方法。
流水线气泡被add操作所代替。
数据冒险
让我们再回到洗衣店的例子。
假设你正在折叠一批衣服,而这批衣服的大多数都是短袜。
你突然发现你的运气非常的不好。
因为在这一批当中的所有短袜的另外一只都在另一批衣服中。
而那一批正在洗衣机中洗涤。
你无法把手头的这些短袜配对,也就无法对它们进行储存,只有等到那一批衣服也被处理完,因而必须阻塞流水线。
这种问题在计算机当中称为数据冒险:
即一条指令依赖仍然在流水线当中执行的前一条指令的结果。
举个例子来说,假设我们有一条加法指令,它之后紧跟着一条减法指令,而减法指令要使用加法指令的和:
Add$s0,$t0,$t1
Sub$t2,$s0,$t3
在没有干涉的情况下,这一数据冒险会严重的阻碍流水线。
加法指令直到第五步才能写出它的结果,这就意味着我们必须在流水线当中加入三个气泡。
虽然我们可以试图依靠编译器来避免这种数据冒险的发生,但实际上这种努力是失败的。
因为这种相关性的发生过于频繁而且导致的延迟也过于长,因此不可能指望编译器能把我们从这种困境当中解脱出去。
一种最基本的解决方法试图不等待指令执行结束来解决数据冒险问题。
对于上述的代码序列,一旦ALU生成了加法运算的结果,我们就可以将它用作减法运算的一个输入项。
从内部资源中直接提前得到缺少的运算项的过程称为转发(forward)或者旁路(bypassing)。
两个指令中的转发
例题:
对于上述的两条指令说明适用转发如何将流水线步骤连接起来。
图6.7描述了流水线的五步的数据路径。
图6.1中的洗衣店流水线类似,每条指令的数据路径排成一列。
图6.8表示了把add指令执行后的$s0中的值作为sub指令执行的输入的转发连接。
在图6.8中,只有当目标步骤在时间上晚于源步骤时转发的路径才有效。
例如,从前一条指令的内存访问的输出至下一条指令的输入的转发路径就不可能有效,因为那样的话将意味着时间的倒流。
转发可以工作的非常好,其具体内容将在6.4节中详细介绍。
然而它并不能够防止所有的流水线阻塞的发生。
例如,假设第一条指令不是add而是载入$s0寄存器的内容,正如图6.8所描述的那样,由于数据间的依赖,所需要的数据只有在前一条指令流水线的第四步完成之后才能生效,这对于sub指令的第三步输入来说就太迟了。
因此,如图6.9所示,即使使用了转发机制,在载入使用的数据冒险中,流水线仍然不得不阻塞一个步骤。
在6.5节我们将论述如何通过流水线的硬件解决象这类载入问题。
图6.7指令流水线的图形表示。
其基本思想与图6.1中的洗衣店流水线类似。
在本图以及本章所有内容中,我们使用图形符号来代表流水线执行各阶段使用的物理资源。
这些符号在五个流水线步骤中所代表的意义分别是:
IF表示指令的预取,其外方框表示指令的内存;
ID表示指令的解码或寄存器文件的读取,外边的虚线方框表示要读取的寄存器文件;
EX表示指令的执行阶段,外边的图符表示ALU;
MEM代表内存访问阶段,包围它的方框代表数据内存;
WB代表写回过程,包围它的虚线方框代表被写回的寄存器文件。
阴影表示该资源被指令所使用。
因此MEM没有阴影,因为add指令在这一步并不读取数据内存。
寄存器文件或内存上右半边的阴影表示着它们在此步骤中被读取,左半边的阴影表示它们在此步骤中被写入。
因此,由于第二步需要读取寄存器文件,ID的右半边有阴影,而由于第五步中需要写入寄存器文件,WB的左半边有阴影。
图6.8转发的图形表示。
图中的连接表示从
add指令的EX操作的输出到sub指令的EX操作的输入的转发路径。
,从而替换掉在sub的第二步从寄存器$s0读取的值。
图6.9当一条R型的指令之后紧跟着一条需要使用该数据的load指令时,即使使用了转发机制,仍然会产生一次阻塞。
如果不进行一次阻塞的话,从内存访问的输出到执行步骤的输入之间的路径在时间上将是倒着的,这显然是不可能的。
重新排列代码以避免流水线的阻塞
下面代码是164页图3.23中的swap过程的一段,请找出其中的数据冒险。
#reg$t1hastheaddressofv[k]
Lw$t0,0($t1)#reg$t0(temp)=v[k]
Lw$t2,4($t1)#reg$t2=v[k]
Sw$t2,0($t1)#v[k]=reg$t2
Sw$t0,4($t1)#v[k+1]=reg$t0(temp)
将这一段指令重新排列以避免流水线阻塞。
冒险会在第二个lw和第一个sw之间的寄存器$t2上发生。
交换两条sw指令的顺序就能排除掉这个冒险:
lw$t0,0($t1)#reg$t0(temp)=v[k]
lw$t2,4($t1)#reg$t2=v[k+1]
sw$t0,4($t1)#v[k+1]=reg$t0(temp)
sw$t2,0($t1)#v[k]=reg$t2
注意,由于在lw写寄存器$t0与sw读寄存器$t0之间仍然存在着一条指令,所以这样交换之后不会产生新的冒险。
因此,在一台具有转发机制的机器上,执行重新排列的代码段只需要四个时钟周期。
软硬件接口在编译器与硬件的设计复杂性进行折衷的例子中,原始的MIPS处理器通过软件在一条载入指令之后加入一条与之无关的指令的方法来避免阻塞流水线。
这样的载入过程叫做延迟载入。
除440页所提到的四种情况以外,转发还给MIPS系统结构增加新的约束,即每一个MIPS指令都在指令执行结束时写而且只写一个结果。
如果一条指令中需要转发多个结果或者在指令结束以前需要写回多个结果,转发将变得更加困难。
例如,在PowerPC的载入指令中就使用了更改寻址机制(参见第三章175页),从而使得处理器必须能够在每一条载入指令中转发两个结果。
流水线概述的总结
流水线是一种在顺序指令流中利用指令间的并行性的技术,其优势在于,与其它加速技术不同(参见第九章),它对程序员是不可见的。
在以下几节中,我们首先使用MIPS的指令子集lw,sw,add,sub,and,or,slt和beq(见第五章)及其简化的流水线方式介绍关于流水线的一些基本概念,然后讨论引入流水线所带来的一些问题以及流水线在一些典型情况下所能够达到的性能。
重点:
流水线增加了同时执行的指令的数目以及指令开始和结束的比率。
流水线并不能够减少单一指令的执行时间。
一个五个步骤的流水线仍然需要五个周期来完成一条指令。
用第二章56页的术语来描述就是流水线提高了指令的吞吐量而不是减少了单条指令的执行时间。
对流水线的设计者们来说指令集即可能将事物简单化,也可能将事物复杂化。
流水线设计者必须解决结构冒险、控制冒险和数据冒险。
而分支预测、转发和阻塞机制能够在保证得到正确结果的前提下提高计算机的性能。
如果你想更快地浏览本章的话,我们相信,在完成了本节的学习以后,你已经具有足够的背景知识,并且可以直接跳到6.8节与6.9节去了解一些先进的流水线概念,如超标量流水线与动态流水线,并且可以去了解流水线在当前的一些微处理器中是如何工作的。
如果你想更加深入的了解流水线技术,在读完第五章与本节后,6.2节将介绍为实现流水线数据路径需要做出的改变,6.3节介绍流水线数据路径的控制,6.4节中阐述为实现转发对数据路径及其