「快」這個字是個陷阱
上一篇我們看著一條指令完整走過擷取—解碼—執行週期。現在把鏡頭拉遠:真實程式會跑數十億個這樣的週期,而每位工程師最終都會問一個問題:「哪台機器比較快?」「快」這個字聽起來理所當然,卻藏著陷阱。對什麼快?是把一件工作儘快做完,還是每小時做完最多件工作?這是兩個不同的目標,一顆晶片可能贏了其中一個卻輸了另一個。
架構師把「快」拆成兩個精確的概念。回應時間(又稱延遲)是單一工作從開始到結束花了多久——量測一個網頁請求的碼錶。吞吐量則是單位時間內完成多少工作——每秒處理的請求數。摩托車對一位騎士有很好的回應時間;公車對一群人有很好的吞吐量。誠實的第一步,是先說清楚你真正在乎哪一個,因為後面的答案全看它而定。
效能鐵律
這是整個計算機結構中最有用的一條方程式。CPU 花在你程式上的時間,乾淨地拆成三個因子——而且就只有這三個。這就是效能鐵律:CPU 時間 = 指令數 x 每指令週期數 x 週期時間。請慢慢讀,因為每個因子各由不同的人掌控。
CPU time = (instructions / program) x (cycles / instruction) x (seconds / cycle)
\______ IC ______/ \______ CPI ______/ \__ cycle time __/
IC set by : algorithm + compiler + the ISA
CPI set by : the microarchitecture (pipeline, caches, ...)
cycle time set by : circuit design + manufacturing (= 1 / clock rate)
Notice: the units cancel. instr x (cycles/instr) x (sec/cycle) = seconds.指令數(IC)是你程式執行了多少條機器指令——由演算法、編譯器與指令集決定。CPI(每指令週期數)是每條指令平均需要多少個時脈拍子——由微架構,也就是晶片內部如何執行的設計決定。週期時間是一個拍子的長度,是時脈速率的倒數(3 GHz 的時脈,週期時間為 1/3 ns ≈ 0.33 ns)。想更快,你必須在不撐大其他因子的前提下縮小至少一個因子——而「不撐大其他因子」這句話,正是幾乎所有真實世界角力的所在。
一個小小的實例
把它具體化。假設一個程式執行 20 億條指令、晶片平均 CPI = 1.5、時脈為 2 GHz(週期時間 = 0.5 ns)。把三者相乘:CPU 時間 = 2x10^9 x 1.5 x 0.5x10^-9 秒 = 1.5 秒。現在有業務員向你推銷一顆時脈 4 GHz 的晶片——兆赫翻倍!——你很想假設它會把時間砍半到 0.75 秒。別這麼快下結論。
- 再讀一次鐵律:只有週期時間改變(從 0.5 ns 變成 0.25 ns)。IC 與 CPI 是程式與微架構的性質,不是時脈的性質。
- 但把時脈推到 4 GHz 通常迫使設計改變而抬高 CPI——例如更深的管線會更常停頓,使 CPI 從 1.5 升到 2.4。
- 新的 CPU 時間 = 2x10^9 x 2.4 x 0.25x10^-9 秒 = 1.2 秒。翻倍的時脈只換來 1.25 倍的加速——換個工作負載甚至可能更慢。
三個因子如何彼此牽制
效能之所以困難,是因為三個因子彼此耦合——壓低一個,另一個就冒出來。複雜的指令集也許靠每條指令做更多而減少 IC,但每條指令就需要更多週期,抬高 CPI。較簡單的指令集會升高 IC,卻讓每條指令跑得更精簡。這場拉鋸正是老掉牙的 RISC 對 CISC 之爭,而現代晶片把界線弄模糊了:x86 晶片會在內部把複雜指令拆成類 RISC 的微指令,於是對外展示豐富的指令集,內裡卻表現得很簡單。
CPI 也是記憶體藏身之處。一個依序掃過陣列的程式能讓平均 CPI 維持低檔,因為多數存取都命中晶片上的快速儲存;而一個在記憶體裡亂跳的程式得付出漫長的等待,使 CPI 暴增——一樣的指令數、一樣的時脈,卻慢上好幾倍。後面會有整篇談這個,但鐵律已經點出原因:快取行為完全活在 CPI 這一項裡。效能很少是關於少做事,而是關於不要等待。
有一條貫穿全局的指導原則:讓常見情況變快。別為一條只跑一次的指令糾結;把心力傾注在那個跑上兆次的迴圈。同樣的直覺也警告你別去優化對目標無關緊要的數字——再厲害的吞吐量技巧,若使用者感受到的是回應時間,那就一文不值。永遠把你正在調的因子,連回你真正在乎的指標。
誠實地量測:基準測試
因為沒有單一數字能完整捕捉「速度」,架構師改用基準測試:在相同條件下執行一組公認的真實程式。像 SPEC 測試套件這樣的標準,刻意把許多多樣的工作負載綁在一起,好讓沒有任何一支程式能美化一顆晶片。有兩個值得記一輩子的提醒。第一,一個不像你工作的基準測試,幾乎不能告訴你關於你工作的任何事。第二,當心像 MIPS(每秒百萬指令)這種誘人的比率,它數的是指令數,不是有用的工作——一顆晶片可以靠跑很多廉價又沒意義的指令拿到高 MIPS。
當你非得把整套測試濃縮成一個數字時,用幾何平均來彙整它的加速比,而不是普通的算術平均——幾何平均是合併比率的誠實作法,能讓參考機器的選擇無法左右結果。這聽來像鑽牛角尖,直到你發現用錯平均法可能會翻轉「哪顆晶片勝出」。誠實的量測本身就是一門功夫,草率對待它,正是優秀工程師自我欺騙的方式。
為什麼硬體不再有免費的加速
數十年來,效能鐵律有一位慷慨的金主。摩爾定律——觀察到晶片上的電晶體數量大約每兩年翻一倍——不斷遞給設計者更多電晶體,而一條搭檔的法則讓每顆電晶體也變得更好。Dennard 縮放指出,當電晶體變小時,其功率密度維持不變,於是你能一代又一代地拉高時脈而不會把晶片燒熔。週期時間免費下降,程式在你睡覺時自己變快。
大約在 2005 年,這份禮物用完了。Dennard 縮放實質上終結:電晶體仍持續縮小,但功率密度不再維持不變,把時脈再往上推會讓晶片熱到無法散熱。週期時間不再自己下降。如今摩爾定律本身也在放緩。效能鐵律沒有改變——但它最好轉的那顆旋鈕,週期時間,卡死了。
這正是這整座階梯後續存在的最深層理由。如果你無法讓單一核心拍得更快,就只能改用更聰明的管線去降低 CPI,或把工作分散到許多核心與加速器上——這正是 2005 年後多核心晶片與 GPU 接管的原因。但更多核心很少代表等比例的加速(一個叫 Amdahl 定律的事實,我們後面會碰到),而平行化又把難題推給了程式設計者。從此以後,你學到的每一個架構概念,某種意義上都是對這道牆的回應:免費午餐結束了,而效能鐵律精準地告訴我們,現在得靠設計去掙得哪一個因子。