其他
特斯拉遇上 CPU:程序员的心思你别猜
作者丨码农的荒岛求生
来源丨经授权转自 码农的荒岛求生(ID:escape-it)
一段有趣的代码
有这样一段代码:for (int k = 0; k < 10000; k++){
for (int i = 0; i < arr.size(); i++) {
if (arr[i] > 256)
sum += arr[i];
}
}
流水线技术的诞生
1769年,英国人乔赛亚·韦奇伍德开办了一家陶瓷工厂,这家工厂生产的陶瓷乏善可陈,但其内部的管理方式极具创新性,传统的方法都是由制陶工专人来完成,但韦奇伍德研究后将整个制陶工艺流程分成了几十道工序,每一道工序都交给专人完成,这样传统的制陶人不复存在,这便是工业流水线最早的雏形。发扬光大
虽然流水线技术可以说是英国人发明的,但发扬光大的却是美国人,这便是福特与T型车。特斯拉与流水线
假设组装一辆特斯拉需要经过:组装车架、安装引擎、安装电池、检验四道工序,同时假设每个步骤需要 20 分钟,因此如果所有工序都由一个组装站点来完成,那么组装一辆特斯拉需要80分钟。但如果每个步骤都交给一个特定站点来组装的话就不一样了,此时生产一辆车的时间依然是80分钟,但这只是第一辆车所需要的时间,此后工厂可以每20分钟就交付一辆特斯拉。CPU 与超级工厂
其实 CPU 本身也是一座超级工厂。机器指令与流水线
实际上说 CPU 生产机器指令是不正确的,CPU 其实不是在生产机器指令而是在处理机器指令,生产机器指令的是编译器,CPU需要处理机器指令以此来指挥整个计算机系统工作。同生产一辆特斯拉需要四道工序一样,处理一条机器指令大体上也可以分为四个步骤:取指、译码、执行、回写,这几个阶段分别由特定的硬件来完成 (注意,真实 CPU 内部可能会将执行一条指令分解为数十个阶段)。当 if 遇到流水线
实际上 CPU 内部的流水线和现实中的并不完全一样。程序员在代码中编写的 if 语句一般会被编译器翻译成一条跳转指令,if 语句其实起到一种分支的作用,如果条件成立则需要执行if内部的逻辑,否则不执行;因此跳转指令会依赖自身的执行结果来决定到底要不要跳转,这会对流水线产生影响。有的同学可能不明白,这能产生什么影响呢?现在,让我们仔细观察一下特斯拉流水线,你会发现当前一辆车还没有完全制造完成时后一辆车就已经进入到流水线了。预测未来
对此 CPU 当然是不知道的。那么该怎么办呢?很简单,一个字,猜。你没有看错,CPU 会猜一下 if 语句可能会走哪个分支,如果猜对了流水线照常继续,如果猜错了,对不起,流水线上已经执行的后续指令全部作废,因此我们可以看到如果CPU猜错了会有性能损耗。现代 CPU 将“猜”的这个过程称为分支预测。当然,CPU 中的分支预测并不是简单的抛硬币式的随机瞎猜,而且有特定策略,比如可能会基于执行跳转指令的历史去进行预测等等。知道了分支预测就可以解释文章开头的问题了。程序员的心思你别猜
现在我们知道,程序员编写的 if 语句对应的是跳转指令:if (arr[i] >= 256) {
sum += arr[i];
}
编写 If else时需要注意什么
实际上如果你编写的if语句没有位于对性能要求很高的核心代码部分,那么分支预测失败这种问题无需关心。实际上现代 CPU 的分支预测是很聪明的,对于非核心部分的if 语句分支预测失败带来的性能损失可以忽略不计。但是对于文章开头提到的代码,程序的大部分时间都用在了 for 循环中,这时你就要注意了,当然前提还是这段代码对时间要求非常严苛,否则你也没必要为了这点性能去优化。好奇的同学可能会问,如果给定的数组是无序的,那么上面提到的这段该怎么优化呢?性能优化
实际上非常简单,只需要移除 if 语句就可以,该怎么移除呢?没有 if 语句的话,那么 sum 每次都必须加上一个数,如果arr[i]比256大,那么 sum 加上差值,否则 sum 加 0即可,这样就消除了if 判断。我们计算arr[i] - 256的值,并将其向右移动31位:(arr[i] - 256) >> 31
sum += ~((arr[i] - 256) >> 31) & arr[i];
总结
虽然 CPU 体积很小,只有指甲那么大,但 CPU 可能是人类有史以来建造过的最复杂的东西,在这里实现了很多有趣的功能,程序员只有彻底理解 CPU 才能更好的利用这些功能编写性能优异的程序。希望这篇对大家理解 CPU 有所帮助。2、在内存只有 24KB 的电脑上写操作系统,是怎样的体验?
识别关注我们
了解更多精彩内容
点分享
点点赞
点在看