輕松讀懂移動處理器 CPU微架構(gòu)全解析
當(dāng)然,還有更復(fù)雜的情況:
if (a > 5)
b = c;
else
b = d;
將其按照匯編語言編寫出來:
cmp a, 5 ; a > 5 ?
ble L1
mov c, b ; b = c
br L2
L1: mov d, b ; b = d
L2: ...
這里的第二條指令是一個條件分支指令(ble 是小于或等于轉(zhuǎn)移指令,ble L1 表示如果寄存器 a 小于等于 5 就轉(zhuǎn)移到 L1 這行執(zhí)行 mov d,b 這條指令)。
這條指令抵達“執(zhí)行單元”的時候,位于流水線前段的取指單元和解碼單元肯定已經(jīng)拾取和解碼了若干條指令,但是哪一條指令才應(yīng)該接下來被執(zhí)行呢?應(yīng)該是第三、四條還是第五條呢?
在第二行的條件分支指令完成之前,處理器只能等待。處理器平均六條指令就會遇到一條分支指令,因此流水線設(shè)計帶來的大部分性能提升優(yōu)勢此時會被喪失掉。
為此處理器必須進行“猜測”,按照猜測結(jié)果進行取指并推測哪些指令能開始執(zhí)行,不過這些指令的執(zhí)行結(jié)果并不會被遞交(寫回)直到分支指令的執(zhí)行結(jié)果完成。
如果猜錯的話,這部分指令的結(jié)果就會被扔掉,這也意味著這些指令對應(yīng)的時間或者周期數(shù)會被浪費掉。當(dāng)然如果猜中的話,處理器就能全速運行了。
那么如何去做“猜測”呢?
一個辦法是所謂的靜態(tài)分支預(yù)測,例如在指令編碼格式里留出一個位元作為預(yù)測信息,編譯器編譯的時候,對這個位元進行標(biāo)記,告訴處理器該跑那條分支,不過這對于已經(jīng)采用了不具備這類條件執(zhí)行指令的舊式 ISA 二進制程序來說這樣顯然是不可能的。
另一個辦法就是“運行過程中”進行猜測即動態(tài)分支預(yù)測,通常是采用類似被稱作“片上分支預(yù)測表”的單元來記錄最近分支的地址以及用一個位元指示出哪一條分支是最近是否被采用的。
不過現(xiàn)在大多數(shù)處理器實際上是用兩個位元來作標(biāo)記的,因此單次的分支“不跳轉(zhuǎn)”并不會馬上導(dǎo)致一般的“跳轉(zhuǎn)”預(yù)測出現(xiàn)反轉(zhuǎn)(這對于循環(huán)邊界來說是很重要的)。不過兩位元分支預(yù)測并有考慮到分支相關(guān)性,所以人們后來有采用兩級分支預(yù)測來解決這個問題,使得預(yù)測精度大大提高。
動態(tài)分支表需要占用相當(dāng)可觀的芯片面積,但是另一方面來說分支預(yù)測對流水線化處理器是相當(dāng)重要的,所以是物有所值的。
不過就算是最好的分支預(yù)測技術(shù)也可能會猜錯,對于超級流水線或者說深流水線來說就會有很多指令的結(jié)果會被扔掉,這樣的情況被稱作 mispredict penalty(誤預(yù)測性能懲罰)。
像 Pentium III 這類具備非常先進分支預(yù)測技術(shù)的處理器,在遇到分支預(yù)測失敗的時候,也會出現(xiàn) 10~15 個周期的性能損失,因此即使正確命中了 90% 的分支,也會因為分支誤預(yù)測導(dǎo)致 30% 的性能損失,所以 Pentium III 其實很多時候會出現(xiàn) 30% 的時間在走冤枉路。
人們在 ISA 中引入條件執(zhí)行指令(predicated instruction),希望籍此盡量減少分支,例如上面的例子,引入名為 cmovle 的判定指令后,可以寫成這樣:
cmp a, 5 ; a > 5 ? mov c, b ; b = c cmovle d, b ; if le, then b = d
cmovle 的作用是“當(dāng)小于或者等于的時候就進行賦值”,只有在條件為“真”的時候才會遞交執(zhí)行,因此被稱作條件執(zhí)行指令。
采用了判定指令后,原來的 5 條指令變成 3 條,避免了兩條分支指令,cmp 和 mov 可以并行執(zhí)行實現(xiàn) 50% 的性能提升,消除了分支預(yù)測錯誤導(dǎo)致的大量誤預(yù)測懲罰。
ARM 從一開始具備完整的判定指令集,而 MIPS 和 x86 后了也都添加了條件賦值指令,IA64(EPIC)中幾乎每條指令都具備條件執(zhí)行功能。
關(guān)注我們
