处理控制冒险的分支预测技术

分支预测

f8af6cf0-3074-4646-b4c0-7bdf631471a4

分支预测技术是用于处理控制冒险的。这里借上图中的一段高级语言的IF-ELSE程序,及其对应的某种汇编语言来讨论分支预测,汇编包含1个条件跳转指令(BR)1个无条件跳转指令(BNE)

首先,上面的无条件跳转指令看起来似乎容易处理,因为其目的地址不会改变,似乎能直接让取值单元取目的地址的指令,但在很多计算机里并不能这样,这由流水线本身的特点决定。在这样的计算机里,当译码单元“发现”无条件转移指令时,取指单元已经在取其后面的指令了。换句话说,无条件跳转的下一条指令已经上了流水线。这时解决的办法是干脆让这条指令执行完,然后再去执行跳转到的指令。这种情况是很难让人接受的,因为完全不符合前后次序。不过有2种“通融”方式:

1) 默认把所有无条件转移指令的下一条指令“当作”其上一条指令来执行;

2) 让所有无条件转移指令的下一条指令都是NOP;

这两种“通融”方式是编译器完成的。(2)中NOP所在的位置又称为延时槽。像INTEL酷睿等现代CPU中就没有延时槽,其通过一些复杂的方式避免了这个问题。

然后再看条件转移指令,条件转移有更大的动态性。上面的这类计算机在遇到条件转移指令时,也是先让延时槽的指令执行,但接下来并不知道要不要跳转,比如MIC-4的流水线要走到很深的位置才知道是否跳转,这时需要让流水线阻塞数个时钟周期。

因此人们在计算机中加入“预测”是否跳转的方法,也就是分支预测。举个简单的预测方案为例:“向当前指令之前的指令的跳转都会发生,之后的都不发生”。前一段成立的理由是向之前的位置跳转的指令很大概率是在循环尾部,而如果在循环尾部那就有很大概率向之前的位置跳转。后一段的假设就比较牵强了,因为很多向之后指令的跳转都是对发生错误的处理,但也有很多并不是这样。

除了提高预测的正确率,还有个麻烦问题,预测失败后如何恢复到之前的状态?计算机状态由内存寄存器(指令层可见)决定。如果流水线错误分支的指令暂未影响到寄存器内存,就相当于指令没执行,清空流水线就能恢复状态。但如果影响到了寄存器就需要进行处理,有这2种策略:

1) 在写入寄存器时,让数据实际写入秘密寄存器,确认预测成功后再替换掉相应寄存器的值;

2) 写入寄存器前就备份寄存器到秘密寄存器中;

这2种方式的实现都很困难,且还有很多其他问题。会不会影响到内存?分支预测在还未确认结果前,又一次需要分支预测怎么办?这些问题很麻烦就不研究了,接下来只讨论分支预测策略本身。

 

动态分支预测

2b10f657-9dd7-4d7f-ba97-da86c2902a07

6eda98d8-8e9f-4b0c-b4a6-3789d0523670

动态分支预测就是根据条件跳转指令的历史跳转情况预测,其中一种方案是让CPU通过额外硬件维护一张历史表,这种表的具体格式可以有很多。上图(a)是种简单的历史表,每项包含有效位条件跳转指令地址预测位。有效位表示该记录是否有效、预测位表示该指令上次是否跳转

由于历史表大小固定,所以不能让每条指令都有自己的项。可以用跟缓存类似的替换数据策略,比如借鉴直接映射缓存,构造指令地址项索引的映射,比如表的项数为k,那么这个映射可以是条件跳转指令(操作码)的地址的mod(k)。如果想减少地址冲突,可继续借鉴组相联缓存的方式横向扩容。

有了历史表后,分支预测的方式本身就很简单了,可以分两种情况处理:

1) 该项无效或非当前条件跳转指令的记录,按前面提到的“向前/向后跳”的方式预测;

2) 该项有效且指令在历史表中,则根据预测位跳转。若预测失败,则更新该项的预测位;

该方案处理循环时有些问题,进入循环后,循环对应的条件跳转指令更新为“跳转”,但循环执行最后一个循环节时,预测结果是错的,且预测位会更新为“不跳转”。如果该循环是某个循环的内层循环,则会更糟,因为循环退出后,紧接着又一次要进入这个循环,但这时预测位是“不跳转”,又一次导致预测失败。也就是说,每次进入/退出内层循环都各导致一次预测失败。

一种解决上述问题的方法是把历史表改成上图(b)。其含两个预测位,第一位表示对跳转的预测,第二位表示上次跳转的情况。两个预测位和实际跳转情况的关系由上图的有限状态机给出。这样就只有每次退出内层循环时有预测错误。该方案容易推广到k位预测位,即k次预测错误后才改变最高位。

某些指令集包含以寄存器内容为目的地址的跳转指令,即目的地址是变量。分支预测既要预测是否发生跳转,又要预测跳转的目的地。这时可将历史表变为上图(c),增加一个字段,记录上次的跳转目的地址。如果预测不跳转但跳转,则更新预测位和目的地址字段。如果预测跳转但不跳转,则仅更新预测位。如果预测跳转但目的地址预测错,则仅更新目的地址字段。该策略曾用于INTEL早期x86处理器。

上面的动态分支预测方案是基于每个指令自身的历史来预测的,还有一类方案是基于之前所有跳转指令的历史,会使用寄存器存储前k次的跳转,并基于此预测。这类方案称为全局历史分支预测

 

静态分支预测

动态分支预测的特点是程序运行时会根据历史调整预测结果,优秀的动态预测方案能表现得很“智能”,但这类方案占用较多硬件(面积)。静态分支预测的特点是程序运行时不会“自己”调整“自己”的预测结果。所以其逻辑相对简单,不会占用太多硬件。比如说“预测所有跳转都会发生”就是一种静态预测方案。

有一类静态预测方案是在ISA指令额外提供预测位。这等于提供了“接口”,把预测问题抛给编译器/程序员。如果编译器遇到了0~9999这样的大循环,肯定是把预测位设为“预测跳转”最划算。

还有一类静态预测方案是基于模拟的。其不提供预测的“接口”,而是把程序放在“虚拟机”上跑一下,然后搜集运行时的跳转结果,把这些结果用于做分支预测。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部