目录
指令流水线
分支预测模型
分支预测模型案例分析
分支预测器
—静态分支预测
—动态分支预测
—典型预测器
参考资料
指令流水线是为提高处理器执行指令的效率,把一条指令的操作分成多个细小的步骤,每个步骤由专门的电路完成的方式。举个例子: 例如一条指令要执行要经过3个阶段:取指令、译码、执行,每个阶段都要花费一个机器周期,如果没有采用流水线技术,那么这条指令执行需要3个机器周期;如果采用了指令流水线技术,那么当这条指令完成“取指”后进入“译码”的同时,下一条指令就可以进行“取指”了,这样就提高了指令的执行效率。
更多关于指令流水线请参照这篇文章
当包含流水线技术的处理器处理分支指令时就会遇到一个问题,根据判定条件的真/假的不同,有可能会产生转跳,而这会打断流水线中指令的处理,因为处理器无法确定该指令的下一条指令,直到分支执行完毕。流水线越长,处理器等待的时间便越长,因为它必须等待分支指令处理完毕,才能确定下一条进入流水线的指令。分支预测技术便是为解决这一问题而出现的。
StackOverFlow上有一个例子,说明了分支预测的概念。想象有一个铁路分叉道口。
为了论证此问题,让我们回到19世纪,那个远距离无线通信还未普及的年代。你是铁路交叉口的扳道工。当听到火车快来了的时候,你无法猜测它应该朝哪个方向走。于是你叫停了火车,上前去问火车司机该朝哪个方向走,以便你能正确地切换铁轨。
要知道,火车是非常庞大的,切急速行驶时有巨大的惯性。为了完成上述停车-问询-切轨的一系列动作,火车需耗费大量时间减速,停车,重新开启。
既然上述过车非常耗时,那是否有更好的方法?当然有!当火车即将行驶过来前,你可以猜测火车该朝哪个方向走。
如果猜对了,它直接通过,继续前行。如果猜错了,车头将停止,倒回去,你将铁轨扳至反方向,火车重新启动,驶过道口。如果你很幸运,每次都猜对了呢?火车将从不停车,持续前行! 如果你经常猜错,你会花很多时间停滞,回滚和重新启动。
转换思维,考虑一个if语句:在处理器级别,它是一个分支指令:
你是一个处理器,你看到一个分支。你不知道它会走哪条路。你是做什么?您暂停执行并等待前面的指令完成。然后继续沿着正确的路径前进。现代处理器很复杂,管道很长。如果这样等待他们将永远地“热身”和“慢下来”。
有没有更好的办法?你猜猜分支的方向!
如果你每次都猜对了,执行将永远不会停止。如果你经常猜错,你会花很多时间停滞,回滚和重新启动。就像火车一样,这将消耗大量的CPU时间周期,比没有猜测效果更差。
这就是分支预测,换句话说,你尝试识别模式并遵循它。这或多或少是分支预测器的工作方式。大多数应用程序具有良好的分支。因此,现代分支预测器通常会达到> 90%的命中率。但是当面对不可预测的分支而没有可识别的模式时,分支预测器实际上是无用的。
关于分支预测,维基百科描述如下:
In computer architecture, a branch predictor[1][2][3][4][5] is a digital circuit that tries to guess which way a branch (e.g. an if–then–else structure) will go before this is known definitively. The purpose of the branch predictor is to improve the flow in the instruction pipeline. Branch predictors play a critical role in achieving high effective performance in many modern pipelined microprocessor architectures[6] such as x86.
来看StackOverFlow上一个高分案例:为什么排序的数组遍历效率更高?
private static void loopSortArray() {
// Generate data
int arraySize = 32768;
int data[] = new int[arraySize];
Random rnd = new Random(0);
for (int c = 0; c < arraySize; ++c) {
data[c] = rnd.nextInt() % 256;
}
// !!! With this, the next loop runs faster
// Arrays.sort(data);
// Test
long start = System.nanoTime();
long sum = 0;
for (int i = 0; i < 1000000; ++i) {
// Primary loop
for (int c = 0; c < arraySize; ++c) {
if (data[c] >= 128) {
sum += data[c];
}
}
}
System.out.println((System.nanoTime() - start) / 1000000000.0);
System.out.println("sum = " + sum);
}
执行结果如下:
第一次执行(注释L12数组排序)时间:74.384751155s
第二次执行(打开L12数组排序)时间:12.140216498s
第三次执行(注释L12数组排序,注释L20分之判断)时间:10.738793256s
第四次执行(打开L12数组排序,注释L20分之判断)时间:10.775258662s
比较第一次和第二次结果,可以看出同样的遍历逻辑,有序数组的效率高了好几倍。再看第三次和第四次,当我们去掉遍历逻辑中分支条件后,数组是否有序好像对程序执行效率又没有什么影响。再交叉比较,好像if条件分支和数组排序,在不同情况下的影响都不一样,这是为什么呢。
其实主要原因就是这个if语句:
if (data[c] >= 128) {
sum += data[c];
}
请注意,数据均匀(随机的)分布在0到255之间。当数据排序时,大致前半部分(小于128)的迭代不会进入if语句。此外他们都将进入if语句。这对分支预测器非常友好,因为分支连续多次朝同一方向运行。即使是简单的饱和计数器也会正确地预测分支,除了在切换方向后的几次迭代。
快速可视化:
T = branch taken
N = branch not taken
data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ...
branch = N N N N N ... N N T T T ... T T T ...
= NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT (easy to predict)
但是,当数据完全随机时,分支预测器变得无用,因为它无法预测随机数据。因此可能会有大约50%的错误预测。(不比随机猜测好)
data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, ...
branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N ...
= TTNTTTTNTNNTTTN ... (completely random - hard to predict)
观察结果:
一般的经验法则是避免在关键循环中依赖数据进行分支。(例如在这个例子中)
分支预测技术包含编译时进行的静态分支预测和硬件在执行时进行的动态分支预测。
最简单的静态分支预测方法就是任选一条分支。1)认为branch一定会token;2)认为branch一定不会token;这样平均命中率为50%。更精确的办法是根据原先运行的结果进行统计从而尝试预测分支是否会跳转。
任何一种分支预测策略的效果都取决于该策略本身的精确度和条件分支的频率。
SPARC与MIPS的最早实现(作为第一代商用RISC体系结构处理器)使用单方向静态分支预测:总是预测条件跳转不发生,因此总是顺序取下一条指令投机执行。仅当条件跳转指令被求值确实发生了跳转,则非顺序的代码地址被加载执行。两种CPU都是在解码阶段评价分支指令,取指令占用1个周期。因此分支目标需要两个周期(即经过了去指令、解码两个周期)才能确定。两种处理器都会在分支指令进入流水线的执行阶段时,插入一个分支延迟间隙。分支指令完成流水线的执行阶段,就已经能确定是否跳转,这时就可以决定是后续的顺序出现的指令被继续执行还是跳转到的新指令进入流水。
更复杂的静态预测假定向后分支将会发生,向前的分支不会发生。向后分支,是指跳转到的新地址比当前地址要低。这有助于配合经常出现的程序的循环控制结构。
一些处理器允许分支预测提示出现在代码中。Intel Pentium 4就是如此。但这一特征在Intel此后的处理器中不再支持。
静态预测也用于某些处理器分支动态预测没有任何可用信息时的一个最后的办法。摩托罗拉MPC7450(G4e)和英特尔奔腾4都使用这种技术作为后备。[9]
在静态预测中,所有决策都是在程序执行之前的编译时进行的。
记载分支最近几次命中情况,并根据此动态预测分支的命中情况。
请参照维基百科——分支预测器:
https://en.wikipedia.org/wiki/Branch_predictor#cite_note-dbp-class-report-1
StackOverFlow关于分支预测的案例
https://en.wikipedia.org/wiki/Predication_(computer_architecture)
https://en.wikipedia.org/wiki/Branch_predictor
https://blog.csdn.net/edonlii/article/details/8754724