条件跳转引起的控制冒险虽然也可以通过在流水线中插入空泡来避免,但是当流水线很深时,需要插入更多的空泡。一个20级的流水线为例,如果一条指令需要上一条指令的执行结束才能执行,则需要在这两条指令之间插入19个空泡,相当于流水线要暂停19个时钟周期,这是CPU无法接受的。
如图所示,为了避免这种情况发生,现在CPU流水线在取指和译码的时候,需要对跳转指令进行分析,预测可能执行的分支和路径,防止预取错误的分支路径指令给流水线带来停顿。
根据工作方式的不同,分支预测分为静态预测和动态预测,静态预测在程序编译时通过编译器进行分支预测,这种预测方式对于循环程序最有效,可以根据循环边界反复取指。而对于跳转分支,静态预测就比较简单粗暴了。一般都是默认不跳转,按照顺序执行,我们在编写有跳转分支的程序时,记得把大概率发生的执行代码放在前面。这样就能明显提高执行效率。
动态预测指在程序运行时进行预测,不同的软件,不同的程序分支行为,我们可以采取不同的算法去提高预测的准确率,如我们可以根据程序历史执行路径信息来预测本次跳转行为,常见的动态预测方式有1-bit动态预测,n-bit动态预测,下一行预测,双模太预测,局部分支预测。融合分支预测,循环预测等。随着大量新的应用软件出现,为了应对新的程序逻辑行为,分支预测器也越来越复杂,占用芯片体积越来越大。除了Cache,就是分支预测器体积最大了。
分支预测技术是提高CPU性能的一项关键技术,本质是去除指令之间的相关性,让程序更高效运行,一个CPU性能高不高,不仅取决于流水线多深,主频多高,cache多大,还和分支预测技术息息相关。一个分支预测器好不好,我们可以两个方面衡量,分支判断速度和预测准确率,目前分支预测技术可以达到95%的预测准确率。
2.5.5 乱序执行
我们编写的代码指令序列按照顺序依次存储在RAM中,当程序执行时,PC指针会自动到RAM中去取,然后CPU按照顺序一条条一次执行,这种称为顺序执行。当这些指令前后有数据依赖关系时,就会产生数据冒险,我们可以在指令序列之间添加空指令,让流水线暂时停顿来避免流水线中预期的指令被冲刷掉。除此之外,我们还可以通过乱序执行来避免流水线冲突。
造成流水线冲突的根源在于指令之间存在相关性。前后指令之间要么存在数据冒险,要么产生结构冒险。我们可以通过重排指令的执行顺序,而不是填充空指令来去掉这种依赖。
ADD R2,R1,R0
SUB R4,R3,R2
ADD R7,R6,R5
ADD R10,R9,R8
在上面程序中,第二条sub指令要使用第一条指令的运算结果,要等到第一条ADD指令执行结束,于是就产生了数据冒险,我们可以通过插入空指令来避免。
ADD R2,R1,R0
NOP 被另外的流水线执行
NOP 被另外的流水线执行
SUB R4,R3,R2
ADD R7,R6,R5
ADD R10,R9,R8
通过暂停了流水线2个时钟周期,避免了流水线冲突,当指令序列中国呢存在依赖关系时,就需要在流水线中不断插入空指令,造成流水线频繁的停顿,为了避免这种情况,我们对指令重排。
ADD R2,R1,R0
ADD R7,R6,R5 提前执行,把流水线占着
ADD R10,R9,R8 提前执行,把流水线占着
SUB R4,R3,R2 安排到和第一条指令同一个流水线。
等第一个ADD执行完才能开始执行最后一个SUB,就不存在数据冒险。
支持乱序执行的CPU处理器,内部一般有专门的乱序执行逻辑电路,该控制电路会对当前指令进行分析。看能否提前执行。