JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

CPI 與週期都花到哪去了

效能鐵律有三個旋鈕,而最滑溜的是中間那個:每指令週期數。這篇導覽把 CPI 撬開——一旦把停頓、快取未命中與分支預測錯誤都算進去,週期究竟花到了哪裡;為什麼低 CPI 和快的程式並不是同一回事;以及為什麼 MIPS 會當著你的面說謊。

效能鐵律的中間旋鈕

上一篇導覽給了我們效能鐵律:CPU 時間 = 指令數 x CPI x 週期時間。三個因子,每一個各歸一層所有。編譯器與 指令集架構大致決定指令數;電路設計者與時脈決定週期時間;而微架構——管線、快取、分支預測器——大致決定那中間的因子,每指令週期數CPI)。這篇導覽就住在那個中間旋鈕裡,因為它藏得最多,也解釋得最多。

CPI 就是每一條指令平均花掉的時脈週期數,攤在整個程式上量:總週期數除以總指令數。把它翻過來你就得到 IPC(每週期指令數),同一件事換個方向讀——CPI 等於 0.5 就是 IPC 等於 2,CPI 等於 4 就是 IPC 等於 0.25。兩者都是平均值,而「平均」這兩個字承擔了很多。沒有哪一條單獨的指令有「一個 CPI」;CPI 是當你把每一條指令真正的成本混在一起、依各種指令各自跑的頻率加權後,掉出來的那個數。

週期究竟花到了哪

這裡有一幅讓 CPI 變誠實的畫面。想像一顆理想的管線處理器,每個週期完成一條指令——完美的 CPI 等於 1,正是當初五階段管線兜售的那個夢。真實的 CPI 是那個理想下限,加上每當機器無法讓生產線繼續前進時所浪費掉的週期。每一次停頓、每一個氣泡、每一次等待,都疊加到基底上。所以 有效 CPI最好讀成一堆懲罰疊在一個理想之上的總和,而效能這門手藝的全部,就是把那些懲罰縮小。

那些多出來的週期從哪兒來?它們幾乎全是這座階梯前面幾級的鬼魂。一個載入使用相依會在某條指令需要它前面那個載入還沒抓完的值時,把管線凍住一個週期。一個被分支預測器猜錯的分支會觸發一次清空,把錯誤抓進來的指令全扔掉,花掉好幾個週期。而最重的那個遠遠領先:一次快取未命中在等待慢吞吞的 DRAM 時,可以把核心停上數百個週期。這些每一個對 CPI 貢獻的那一片,都正比於它發生的頻率乘上它所花的週期數。

Effective CPI = ideal CPI + sum of (event rate x event penalty)

Worked example (1 GHz core, ideal pipelined CPI = 1.0):

  source         frequency (per instr)   penalty   CPI added
  ----------     ---------------------   -------   ---------
  ideal base                  --             --       1.00
  branch mispredict    2% of instrs      15 cyc       0.30
  L1 cache miss        5% of instrs      12 cyc       0.60
  L2 cache miss        1% of instrs     100 cyc       1.00
  ----------------------------------------------------------
  effective CPI                                        2.90

So the 'ideal' 1.0 machine actually runs at CPI 2.90 --
it spends about 66% of its cycles NOT making forward
progress. The cache misses alone cost more than the
useful work. THIS is 'where the cycles go'.
有效 CPI 是理想下限加上一疊懲罰,每一項都依它觸發的頻率加權。注意主宰一切的是記憶體系統,而不是 ALU——大多數週期花在等待,而非計算。

盯著那張表,這個教訓會把一切重新框定。那台「每週期一條指令」的機器,實際上平均每條指令花 2.9 個週期,而最大的元兇不是算術——它是我們兩級之前見過的記憶體牆。這就是為什麼架構師執著於快取與分支預測,而不是更快的加法器:加法器從來不是瓶頸。這也是讓常見情況變快的深層理由——你去攻擊那個佔最多週期的懲罰,而在大多數程式上,那就是記憶體系統。

依指令組成拆解 CPI

拆解 CPI 還有第二種、互補的切法:不是依停頓來源,而是依指令的類型。不同類別的指令確確實實花不同數量的週期。一個暫存器對暫存器的加法很便宜;一個整數乘法花好幾個週期;一個浮點除法花很多;一個在快取裡未命中的載入花數百個。整個程式的 CPI,是這些各類別成本的加權平均,而權重就是程式裡每一類各佔多少——它的指令組成

  1. 列出你程式裡的指令類別(比如:ALU 運算、載入、儲存、分支),以及每一類佔已執行指令的比例——那就是指令組成。
  2. 對每一類,找出它在這個微架構上的平均週期成本,連同它傾向觸發的停頓在內——分支揹著它的預測錯誤稅,載入揹著它的未命中稅。
  3. 把每一類的頻率乘上它的週期成本,再把這些乘積加起來。那個加權總和就是程式整體的 CPI——和硬體計數器會回報的數字一樣。
  4. 現在你可以把力氣對準目標:頻率乘成本的乘積最大的那一類,就是最佳化最划算的地方。把佔 2% 週期的那一類砍半幾乎撼動不了指針;把佔 60% 的那一類砍半,則會脫胎換骨。

這兩種拆解——依停頓來源、依指令組成——是同一筆總週期數用兩種方式去數,而好的效能工作會兩個鏡頭都用。組成這個視角告訴你什麼樣的工作佔主導;停頓這個視角告訴你什麼東西在阻止那些工作流動。兩者合起來,回答了這篇導覽以它命名的那個問題:當你問週期花到了哪去,你能把它們一個一個地點名出來,一項懲罰、一個指令類別地,而不是對著一句神秘的「感覺很慢」揮手。

為什麼 MIPS 與 FLOPS 會誤導

現在我們可以拆穿一個誘人卻陰險的指標。MIPS——每秒百萬條指令——聽起來像個乾淨俐落的速度評分:越大越快,總沒錯吧?可是 MIPS 等於時脈頻率除以(CPI 乘一百萬),所以它數的是退役的指令,而不是完成的工作。而指令並不等於工作。兩台機器在解同一個問題,可以回報出非常不同的 MIPS,只因為它們的指令集把這件事剁成了不同數量的指令。

更糟的是,MIPS 可能往錯的方向動。一個把慢的指令序列換成快的指令序列的編譯器最佳化,通常會縮小指令數——於是程式更早完成,它的 MIPS 評分卻下降了,因為它現在每秒退役的指令變少,儘管每一秒做的真實工作變多。一個在你真正加速程式時反而下降的指標,量的不是效能;它量的是指令的攪動。誠實的玩笑自己就寫好了:MIPS,「處理器速度的無意義指標」(Meaningless Indicator of Processor Speed)。

FLOPS——每秒浮點運算數——是同一個陷阱穿了件實驗袍。它比 MIPS 有用,因為一個浮點運算更接近真正的科學工作,這也是為什麼它主宰著超級電腦的排名。但它數的仍然是某一種運算,而不是解你問題的時間,而且它常常被鑽空子:一個核心可以在算著某個你根本不需要的東西時、或在記憶體頻寬被餓住以致浮點單元多半空轉時,飆出一個令人目眩的尖峰 FLOPS。一再重複的教訓,就是上一篇導覽那個百萬赫茲迷思的推廣版:任何數代理量的指標——赫茲、指令、浮點運算——都能在不讓你的程式更早完成的情況下被灌水。

那到底哪個指標才對?

這裡是誠實的綜合結論,也是整個這一級的脊樑:沒有一個唯一正確的效能數字,因為對的指標取決於你真正在乎什麼。如果你在乎一件工作要花多久,你要的是執行時間——效能鐵律給的那個答案——而 CPI 是理解它的工具,本身不是目標。如果你服侍著數以百萬計的請求,你在乎的也許是吞吐量尾端延遲,遠勝過任何單一工作的 CPI。如果你的手機在你口袋裡,你在乎的也許是每件工作的能量勝過一切,那是接下來幾篇導覽要接手的。

把這些握在一起,效能鐵律就不再是一條公式,而成了一張地圖。指令數、CPI、週期時間把每一份加速都劃分開來,而 CPI 又進一步劃分成一個理想下限加上一疊叫得出名字的懲罰。當有人說某顆晶片「比較快」,你現在知道該問:在什麼事情上比較快、怎麼量的、三個因子裡動了哪一個?這份拒絕被單一個閃亮數字唬住的態度,正正是下一篇導覽要鍛造成一門紀律的——不自欺地做基準測試。