参考来源:《超标量处理器设计》—— 姚永斌
分支预测失败时,这条分支指令之后的所有指令都处在了错误的路径上(mis-prediction)。
这些指令都会被抹除掉 ,从而造成很多bubble,降低处理器性能,称之为分支预测失败时的惩罚(mis-prediction penalty)。
这些处在错误路径的指令有可能已经将处理器中某些部件的内容进行了修改,例如寄存器重命名阶段的重命名映射表(mapping table)。
因此需要对这些行为进行撤销,也称之为分支预测失败时的恢复(mis-prediction recovery)。
流水线不同阶段的分支预测检查
解码阶段
例如发现了一条无条件直接跳转指令(MIPS中的J指令),则这条分支指令的方向和目标地址都确定了。
如果发现这条分支指令之前被预测为不可跳转,就会发生预测失败。
但是如果在解码阶段发现预测失败,代价还是可以接受的,因为可以马上进行修复,惩罚也最小。
如果是间接跳转(MIPS中的JR指令),虽然可以判断预测方向是否为跳转,但是对寄存器中的内容无法确定。
因为读取寄存器过程在流水线后续阶段发生。
因此即使知道预测失败,也无法确定正确的取指地址。
此时可以简单暂停流水线,避免后续抹除指令造成的功耗浪费。
读取物理寄存器阶段
此阶段可能发生在寄存器重命名阶段之后,也可以发生在发射阶段之后,根据具体架构决定。
如果此时读取到了寄存器的数值,那么可以对间接跳转的预测目标地址进行检查。
如果目标预测错误,则使用正确的地址进行重新取指。但是这个时候仍然需要分支后流入流水线的指令全部清除,但是指令也可能已经进入发射队列。
执行阶段
不管什么类型分支指令都可以被计算出结果。一旦发现预测失败,需要抹除之后进入流水线的全部指令。并且由于超标量处理器采用乱序执行的方式,流水线中还有部分指令是在分支指令之前进入流水线的。
这些指令可能位于发射队列或者执行阶段,因为指令原有的顺序记录在重排序缓存ROB中,每条分支指令都有一个编号,因此可以通过ROB对分支预测失败的指令进行状态恢复。
所有流水线中的分支指令编号都存放在一个FIFO中,它的容量等于处理器最多能支持流水线中的分支指令个数。
一旦满了就不能再往流水线中存放分支指令,也就是此时如果解码出分支指令,只能暂停流水线直到编号列表有空闲空间为止。
可以采用两个FIFO,一个存放空闲编号,一个存放使用编号。每一次预测失败或者分支指令退休,都会从使用编号FIFO释放并存入空闲FIFO。
执行阶段预测失败后的恢复方法
基于ROB的暂停流水线取指
当执行阶段发现一条分支指令预测失败时,将这个信息记录在ROB对应的表项中,并暂停流水线取指,但是保持流水线继续执行。
直到这条分支指令成为最旧的指令时,代表这条分支指令之前进入流水线的指令都已经退休,再将整个流水线抹除,从正确的地址开始取指。
虽然这个方法容易实现,硬件复杂度不高,但是需要暂停流水线,等待指令退休。一旦出现D-Cache miss的load指令,就会导致损耗很大。
基于Checkpoint方法恢复
在分支指令之后的指令更改处理器状态之前,将处理器状态保存起来。
保存的内容包括了寄存器重命名中的映射表以及分支指令下一条PC等等。
这种方法虽然耗费很多资源,但是恢复阶段的效率更高。
抹除流水线
执行阶段预测失败后的抹除流水线包括两部分:
- 抹除发射阶段之前的所有指令,因为发射之前都是In-Order顺序执行。
- 发射阶段以及之后的阶段通过编号列表tag list区分处于预测失败编号内的指令,通过广播比较编号。
但是指令抹除并不需要在一个周期内全部完成,因为一条分支指令预测错误后,需要重新从正确地址取指。
新的指令需要经过流水线好几个阶段才能达到发射阶段。
所以只需要在新的指令到达发射队列之前完成错误分支指令的流水线抹除即可。
所以每次广播错误预测分支指令编号数量都不会很多,保证在几个周期内完成即可。
Tag List 编号分配
对于N-Way超标量处理器而言,如果一个周期内解码的N条指令都是分支指令,那么空闲的编号列表 Tag List 就需要在一个周期内提供N个编号数值(多端口FIFO)。
但是这种会增加芯片的使用面积和功耗,并且绝大多数情况这类FIFO都是空置状态,利用率不高。
因此大部分处理器都会限制流水线解码阶段最多处理一条分支指令,如果从解码缓存(Instruction Buffer 取指与解码之间的缓存)解码出多一条分支指令,那么第二条分支指令及其后续指令都会延迟下一个周期。
因为分支指令在大部分程序中并不密集,因此这种方法对处理器性能不会影响太大。
而这个用于保存分支指令的缓存,主要可以存储预测结果,也就是只将预测为跳转的目标地址放在缓存中。
还有一种特殊情况,就是当程序中存在自修改(self-modifying)的代码,可能将一条分支指令修改为普通计算指令。
此时需要将分支预测器内容清空,否则会发生此条计算指令预测为跳转,造成性能损失。
超标量处理器的分支预测
超标量处理器中,取指得到的一个地址,会从I-Cache中取出多条指令,组成一个指令组(Fetch Group)。
因此超标量处理器中的取指令地址并不是连续的,每次增加值等于指令组的字长。
如果只对取指令的地址进行预测,相当于只对指令组中的一条指令预测,而不是对整个指令组预测,就会降低分支预测的准确度。
因此要预测整个指令组,就需要指令组中所有指令的PC值。
以指令组长度为4举例:需要通过加法器获取PC + 4/PC + 8/PC + 12的数值。
由于在一个周期内完成,所有指令都会在一个Cache Line之内,因此可以实现同时预测分支指令的方向以及跳转地址。
但是这样等于需要一个周期内提供4个PC预测数值对应地址,但往往只会用到一个端口(分支指令并不密集)。
如果在分支指令方向预测结束后再计算预测地址,会导致分支预测无法在一个周期内完成。
对于RISC指令集而言,大部分跳转指令的目标地址可以在取指阶段就可以被计算出来,并在进入I-Cache之前进行预解码区分分支指令。
对于超标量处理器需要的多端口场景,也可以通过交叠(interleaving)的方式让单端口存储器支持多端口功能。