一、说明
在网上看到了一个问题,有人问“既然LLVM中实现了指令调度,而CPU又可以乱序执行,那二者有什么区别联系呢”?网上的回答很是不错,但技术性太强,不易为广大的开发者一眼看明白。这里就对这个话题进行一些直白些的分析和说明。
其实从早期的编译器和CPU的情况来看,这种问题并不是问题。主要原因在于二者都很LOW,基本无法出现这种问题的情况。说白了就是各自地盘的事儿都忙不过来,哪有时间向别人的地盘伸手。而随着硬件的快速发展和编译器的智能化程度不断增强,这种问题才成为了问题。
二、编译器中对指令的调度
LLVM的指令调度属于编译器层次的处理,它是基于代码的静态分析后的指令优化调度。所以在这个阶段只能对编译时获得的相关信息进行相关的指令优化,它主要目的是尽量减少指令等待和数据依赖,从而能够更好匹配硬件资源。比如寄存器的是否使用,内存之间的分配依赖等等。特别是减少相关指令操作顺序和路径的处理,从整体上规划指令的流程。通过静态的分析,将可分离和互不依赖的指令与其它指令区分开来,这样就可以为CPU动态执行指令时创造各种基础的条件。
编译时指令的调度是一种无法与实际运行完全一致的一种优化调度,它付出的代价(特别是编译时间)较高,在某些情况下,可能会导致有额外指令的插入。大家可以把编译器对指令调度当作是计划的制定者和完善优化者。计划执行的结果会保持一致,但计划执行的细节可能与制定的不完全一样。
三、CPU的乱序执行
大家都明白,再好的计划也不可能与实际完全吻合。或者说,这次完美的计划可能在下次就不能完美的执行了(比如开发者一边听歌,一边下载,一边在调试程序,就可能有这种情况)。CPU作为真正的指令执行者,它除了受到指令的影响还受到比如电压、电流和其它一些意外情况的影响。就如一个长跑者,虽然平时表现都不错,但在真正跑的那天,可能肌肉或关节等会有一些小伤暴露出来。那么就非常可能表现不出平时的状态来。CPU也是如此,一些内存的状态、缓存的命中、内存访问延迟等等都是无法控制的,此时就有可能会出现一个指令受限于资源或外界影响等会被啰一个指令优先执行,从而充分的利用运行单元等相关资源。即让计算机的各种资源得到尽可能的最大利用。
CPU的执行速度极快,能力强大(即实时的处理任务并能看到任务指令的数据依赖和资源冲突)而且相对于软件是透明的。CPU的乱序执行,大家可以认为是一个具体执行者根据实际情况发展而对整体计划的一种不断优化执行的过程。
给大家举个例子,分配一个工厂生产一百个零件,制定了最优的计划。但在具体执行时,突然出现停电和工人请假等情况。而车间主任通过让其它工人加班和请回其它休假工人等方式反而提前完成了任务是一个意思。
要想深入的掌握CPU乱序和编译期的指令调度(比如分支预测、超标量、任务调度优化等等),就需要开发者对编译的相关技术和硬件体系(memory_order,happens-before等)的相关技术进行较深入的了解,而这一般就不是普通程序员愿意花费大量精力去学习和研究的技术了。
四、二者之间的关系
二者间的关系更类似于一种不同军种之间配合打仗一样。整体的军事计划中谁指挥谁已经定好的。但在实际的战斗过程中,会出现各种意外情况。具体的场景下,临时你可能指挥我,而我也可能指挥你。这也是打仗不能僵化、教条化的一种体现。
具体来说,LLVM的指令调度与CPU的乱序执行是一种互相有益的补充,即一种可以发挥各自最大特长的应用情况。编译器通过静态获取的相关硬件(CPU架构等)、软件、库以及其它一些特定的因素,从整体上系统的对指令进行处理(包括指令重排),让其趋于更加合理和更高效的契合当前的硬件资源。但有些因素是在静态分析时无法得到的,比如缓存的命中,虽然命中率可能是达到九成,但如果在实际运行时恰恰命中了那另外的一成呢?这时,CPU的乱序执行就会动态的调整相关的指令,优先执行准备好的指令。也达到了最大化利用硬件并完成计算指令工作的要求。
也就是说,不管是编译期的指令调度还是CPU的乱序执行,它们的目的都是一样的,二者要尽量保证在任何情况下达到整体的最优化的效果。
再举一个不恰当的例子,一个人吃三个苹果就饱了,但吃了两个后发现没有苹果了,但还有两个桔子。要么等十分钟苹果送来,要么,吃两个桔子也能吃饱,怎么办?对于人来说,它既是计划的制定者又是执行者,所以他可以轻松的转到再吃两个桔子来达到吃饱的目的。但对于机器来说,编译时指定了必须实现三条指令才能执行后续的指令,那么如果没有乱序执行,CPU就会傻傻的等着。结果就是浪费了硬件资源,自己不干活还无法让别人干活。
也就是说,编译期的静态处理指令时做的越完善,那么在CPU动态执行指令时,优化执行指令,提高利用效率的可能性越高。
五、总结
对很多开发者来说,很难接触到编译器中较深的知识,更别提和硬件CPU相关的编译器知识点了。其实这种设计方式完全可以抽象的来看,即不同模块间的设计互相影响又互相制衡。它们间通过接口来实现整体的协调一致性即可。
编译器和CPU也是如此,编译器负责整体的协调和静态的编译展开;而CPU负责局部的优化和动态执行。所以二者是一种互补的存在而不是一种竞争的存在,至少在主流上一定是这样。

1981

被折叠的 条评论
为什么被折叠?



