参考书:《计算机体系结构量化研究方法》 作者:John L. Hennessy
流水线运行的一个主要限制是:它们使用循序指令发射与执行的方式。把指令执行的流水线简单比喻为洗衣服:
采用循序发射与执行方式的限制,意思就是,衣服只能一件一件洗,洗完一件才能洗下一件。如果现在洗衣机坏掉了(硬件故障),或者衣服还没脏(操作数不可用),这个流水线就停顿了,哪怕后面还有指令等待执行。但是我们希望操作数可用的时候就立即执行指令(有脏衣服就洗),这样的流水线实际上是乱序执行的,意味着它将会乱序完成。这就是动态调度的主要思想。
一、Tomasulo算法
我们的目的是让操作数可用的时候立即开始执行。于是我们增加了一个“管家”,如图所示:
Tomasulo算法大概是这样的:
这家主人说我要洗衣服,告诉了管家,这个步骤叫做指令发射。之前不同的是,这个指令由管家保存,并把相应的操作和衣服放在了新木桶中,这个木桶就是算法中的保留站。这个木桶很大,可以存放多条指令。不像之前,必须一条一条指令循序执行。不过有一点要注意,在Tomasulo算法中,指令仍然是循序发射的。主人1,主人2跟管家说我要洗衣服,这个过程是一个一个来的。
但是动态调度是如何实现的呢?
现在新木桶很大,现在里边可以存放多条指令和衣服。但是现在管家发现了,虽然主人1说要洗一件衣服,但是衣服还没来,或者是这件衣服还在洗衣机中洗。那么管家就会在木桶中给主人1留一个位置。因为衣服还没来,没办法洗。这时候管家就会为主人2先洗衣服,同时监视晾衣架上主人1的衣服有没有洗好。一旦发现晾衣架上主人1的衣服准备好了,那么就立即开始为主人1洗衣服。这就实现了:
- 操作数可用时立即执行程序
- 指令的循序发射以及乱序执行
Tomasulo算法的真实结构图如下所示:
下面讲一下算法的细节问题:
看以下下面这串代码:
DIV.D F0,F2,F4 ;F0 = F2/F4
ADD.D F6,F0.F8 ;F6 = F0 + F8
S.D F6,0(R1) ;存储
SUB.D F8,F10.F8 ;F8 = F10 - F8
MUL.D F6,F10,F8 ;F6 = F10 * F8
虽然代码不大,但出现的数据冒险很多,共有5个:
把这个算法写到保留站时,保留站应该如何处理呢?
寄存器重命名!
保留站会把这个代码改写一丢丢,对寄存器进行重命名,使其不在冲突。改写后的代码如下:
DIV.D F0,F2,F4
ADD.D S,F0,F8
S.D S,0(R1)
SUB.D T,F10,F14
MUL.D F6,F10,T
S,T为重命名的寄存器名称,这样就去除了所有的读后写WAR冒险和写后写WAW冒险。但是如果细心点会发现,改写后的代码中还存在写后读冒险。应该如何解决呢?因为刚才也介绍到,保留站只会在操作数可用的时候才会执行程序。如果正在写源寄存器,那么保留站将会先执行其他执行,等待源寄存器写好,再执行此条指令,这样就解决了所有的写后读RAW冒险。总结来说:
- 操作数可用时执行程序,解决了写后读RAW冒险
- 寄存器重命名解决了写后写WAW,以及读后写WAR冒险
另外,在写结果时,先将计算结果写到CDB(公共数据总线)中。然后CDB上进行广播,看这个计算结果有没有作为是其他指令源操作数的。如果保留站发现有,就会更新保留站源操作数的值。这样就代替了流水线寄存器。
二、推测
什么是推测呢?举个例子,现在这家主人说,我要洗衣服,我洗完这件还得洗,不过洗的衣服不确定,要么洗衣服A,要么洗衣服B,或者还有更多的选项。因为管家是提前处理指令的。所以现在他不知道要洗衣服A还是B。就没办发提前处理了。所以现在有两种选择:
1)等待,直到执行完分支指令,计算出分支地址之后,确定了要洗衣服A还是洗衣服B的时候再开始洗。这种就是无推测的执行方法,但是会产生不小的流水线停顿。
2)猜。洗衣服要么是A要么是B(如果只有两分支的话),我猜要洗衣服A,就开始先洗了。这样的提前处理,如果分支猜对了,就节省了很多的时钟周期。但有个问题猜错了话,咋办?
猜错了的话,首先,你得把衣服B还给人家吧。这种方式就是保持异常行为的一种。意思是:你现在程序执行错误,你必须要回到原来的状态,以重新执行正确的程序。但是怎么正确的处理预测错误导致的异常呢?这里增加了一个概念叫:重排序缓冲区(ROB)。它的主要思想是:
- 允许指令乱序执行,但必须循序提交
我们Tomasulo算法中实现重排序缓冲区,其思想就变成了
- 指令循序发射,乱序执行,循序提交
用洗衣服的方式来比喻的话,大概是这样的(未展示发射逻辑):
现在又增加了一个管家2,管家2干啥呢?他负责整理并保存已经洗好的衣服,按照他发射的顺序进行排序。现在程序可能会出现,已经执行完毕,但是还没有把结果写回的情况。因为指令提交就必须循序完成,哪怕你已经执行完毕了,也得在重排序缓冲区中等待循序提交。
重加载缓冲区的实际逻辑如下:
现在对于推测错误就很好处理了,因为哪怕已经执行了分支之后的命令,由于之前的指令还没有执行完毕,分支之后的指令就无法提交,所以很容易的就程序计数器改为正确的分支地址,而不影响其他指令结果。
实际上,预测错误的概率还是相对较低的,在Intel Core i7的分支预测器中错误预测率平均在5%以下。