以下内容由我带过的一个学生,现在Marvel公司新入职工程师Beck Zhang撰写,转帖的人请注明,尊重作者原创知识产权。
最近刚开始接触到Super Scaler和Out of Order Execution的处理器,比起在学校里做的单流水线顺序执行的处理器要复杂许多,特别是在微架构(Micro Architecture)上,控制逻辑复杂得让人头疼。事实上,乱序执行技术是已经非常成熟而被广泛应用的技术,比如ARM cortex-A9就是一款采用乱序投机执行方式的超标量处理器内核。下面记录的是鄙人在学习过程中的点点滴滴,纯属个人的理解,如有错误之处,请大家批评指教。
一. 为什么要使用乱序执行?
在早期的处理器中,处理器执行指令的顺序是按照程序员写好的汇编代码的顺序进行的,这种执行顺序叫做按序执行(In-Order Execution)。按序执行在微架构上实现起来比较简单,不过也存在着性能上的缺陷。打个比方来说,“进攻基本靠走,停球基本靠手,过人基本靠吼,防守基本靠搂”中国国家足球队在以1比5的比分输给泰国队之后,教练为了稳固队内球员的信心,准备与A、B、C三支女子业余足球队进行友谊赛。为了让球员在每场球赛之后能够恢复体力,三场友谊赛的赛程如下安排:
A业余女子足球队 VS 中国国家足球队 2013年7月30日
B业余女子足球队 VS 中国国家足球队 2014年1月30日
C业余女子足球队 VS 中国国家足球队 2014年7月30日
恰巧在2013年7月30日前几天,A业余女子足球队由于种种原因,不得不将比赛推迟一年。这样的话,如果不改变三场比赛的比赛顺序,那么B和C业余足球队也会因此将把约定的比赛日期推后一年。而在这一年当中,中国国家队和BC两支女子业余足球队都处于无比赛的空闲状态。
这时,机智的国家队主教练提出了一个改变比赛顺序的方案:
B业余女子足球队 VS 中国国家足球队 2013年7月30日
C业余女子足球队 VS 中国国家足球队 2014年1月30日
A业余女子足球队 VS 中国国家足球队 2014年7月30日
在A业余女子足球队休息的一年时间里,先和BC两支足球队进行比赛,等到A队的队员能够参加比赛时,再与其比赛。这样不仅使中国国家队得到了背靠背的热身训练,也充分节约了ABC三支业余女子足球队的时间。
以上仅供玩笑。在指令的执行过程中,往往也会出现一些不可避免的等待情况,比如后一条指令的源操作数依赖前一条DIV指令的结果(Data Dependancy)或者是代码中存在跳转指令,甚至一些更为严重的情况,如访存操作时出现TLB Miss或者Cache Miss。在按序执行的处理器中,不得不使流水线中断若干个cycle,处理器陷入了等待状况,后面的指令都得不到执行。李白说得好:“天生我才必有用”,虽然国家队主教练总是在输球之后把原因归结于球员水土不服,但是说不定他在处理器设计上却存在着惊人的天赋。将他“改变比赛顺序”的思想应用在处理器上,就会在一定程度上解决流水线中断的问题:如果后面的任务不依赖于前面的任务,并且现在已经有能力进行后面的任务,就先进行后面的任务。处理器中乱序执行的思想就是:打乱指令的顺序,不管指令位于前边还是后面,只要指令可以被执行,就先执行。
所谓“只要指令可以被执行”,主要是两个方面:
1. 该指令的所有操作数是否已经准备好;
2. 是否有空闲的执行单元(Execution Unit)来执行这条指令。
一条指令如果满足了上述两个条件,就可以被执行,而不需要去等待前面的指令。下面是一个例子,如果一段代码:
1 LDR R1, [R0];
2 ADD R2, R1, R1;
3 ADD R4,R3,R3;
从代码中可以看出,第2条指令的源操作数R1依赖于第1条指令的运算结果,而第1条指令的运算结果又长时间没有被计算出来,这时,就可以先执行第3条指令,因为第3条指令不依赖于之前的指令。
总结:采用乱序执行可以在一定程度上减少由数据依赖等问题带来的性能缺失,提高了处理器的指令执行效率。
不过,乱序执行也会带来诸多问题...