您可能是分支预测的受害者!

背景

现有一个长度N=1000000数组 a[N],每个元素的取值范围为0-255。要求将小于128的元素全部设置为0,大于等于128的元素设置为1

我们很容易写出这样的循环遍历代码

for (i = 0; i < N; i++) {
    if (a[i] < 128) {
        a[i] = 0;
    } else {
        a[i] = 1;
    }
}

思考一个问题,数组a无序和有序,会对这段代码的执行速度造成影响吗?乍看之下似乎不会,但实际执行时间可能相差3-4倍。

为什么同样是遍历数组,判断元素。遍历有序数组的速度会比无序数组快几倍呢?

您可能是分支预测的受害者!

火车

考虑这样一个火车分岔路口,需要一位管理员拨动操作杆,来让火车向左或者向右继续前进。
但是这个管理员并不知道过来的火车想要往哪个方向前进。那目前有两种方法:

1、火车到达路口时停下,告诉管理员它想要前进的方向,管理员拨动操作杆,火车重新启动。耗时5分钟

在这种情况下,好处是火车永远不会走错方向,坏处是每辆火车都需要停下来重新启动

2、火车到达分岔路口前,管理员随机猜一个方向。
(2-1)如果猜对了,火车无需减速,直接同行,耗时0分钟
(2-2)如果猜错了,火车驾驶员会骂骂咧咧的倒车,然后重新驶向正确的方向,耗时10分钟

考虑两个极端情况:管理员总是猜对,那么火车的通行效率将变得非常高
管理员总是猜错:火车同行效率变得异常低(这运行也太差了)

正常情况是:管理员总会有50%的可能猜对或猜错,耗时与方案1差不了多少。
管理员只要稍稍做一些总结和思考,比如下午4点的火车,向左走了19次,向右走了1次,那么这次猜左边,就能加大猜中的几率

现代CPU

现代CPU和上述火车的例子类似。火车是命令,管理员是CPU。对于if之类的分支命令,CPU提前并不知道会走向哪个分支。
现代的CPU往往具有多级流水线来处理指令,如果每条命令都需要卡在分支语句处,等待条件判断完成再往下走,效率会很低

于是CPU会先对分支进行预测,在代码还没走到分支时,就提前猜想一个结果并往下执行

如果猜对了,毫无阻塞,继续执行,如果猜错了,则把指令全部清理掉,重新处理。

迭代分支预测器

回到刚才火车的例子,管理员预测下一辆火车将要向左还是向右,如果是完全随机的,那么他有50%的几率猜对,50%的几率猜错,算下来并没有节省多少时间。

一种比较简单的想法是,如果上一辆火车向左,那么就猜这一辆也向左。

利用这种策略,入股有连续多辆火车是驶向同一个方向的,那么通行速度将变得异常快。

其他更高级的算法,往往也保留了这一思想。

回到代码

所以,如果数组a提前排序了,分支预测器就能非常好的工作

数组 1 2 3 .... 126 127 128 129 130 ....
猜测 T T T ..... T T T F F ....
实际 T T T ..... T T F F F ....

分支预测器只猜错了一次!(火车只需要倒车一次,其他时候都能快速通行)

而如果数组是无序的话

数组 1 17 133 7 140 189 9 ....
猜测 T T T F T F F
实际 T T F T F F T

那么猜中的几率就变低了(更多的火车需要倒车)

当然,迭代分支预测器只是最简单的模型,实际的预测器模型会更加复杂,来保证更高的猜中率

下一篇文章将介绍如何通过高级编程技巧来避免成为分支预测的受害者

posted @ 2020-03-27 23:40  Velscode  阅读(413)  评论(0编辑  收藏  举报