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

走向並行:指令級平行、多核心與免費午餐的終結

三十年來,程式設計師免費獲得更快的軟體——每一代新晶片都把同樣的程式跑得更快。然後在 2005 年前後,這頓免費午餐結束了,時脈凍結在 3~4 GHz 附近。本篇講述架構師如何從單一指令流中榨出平行性、為何物理迫使我們轉向多核心,以及為何阿姆達爾定律意味著「核心越多」也並非另一頓免費午餐。

持續三十年的免費午餐

想像你在 1985 年寫了一支程式,跑一次要十秒。你什麼都不做——一行都不改——五年後它只花兩秒就跑完。再等五年,已經不到一秒。這在數十年間真實發生過,程式設計師把它當成理所當然,就像期待太陽升起一樣。它甚至有個名字,由 Herb Sutter 在 2005 年提出:免費午餐。背後的引擎是兩股交織的趨勢。摩爾定律讓晶片上可容納的電路(以電晶體計)每兩年左右翻倍,而登納德縮放則說:當電晶體縮小時,其功率密度維持不變——於是你能把它們跑得更快,而不會把晶片燒熔。

提高時脈頻率是最直接的槓桿:在其他條件相同下,100 MHz 的晶片變成 3 GHz,跑同一支程式就快了三十倍。但架構師還有第二個、更微妙的招數。即使時脈無法再快,他們也能讓處理器在每個時脈週期內做更多事——這正是指令級平行的故事。

從單一指令流中榨出平行性

在第二階我們認識了管線(pipeline):一條裝配線,每條指令依序通過取指、解碼、執行、存取記憶體、寫回等階段。管線已經讓多條指令重疊進行,但最理想情況下每個週期仍只能完成一條。指令級平行(ILP)更進一步:它注意到鄰近的指令往往彼此獨立——不依賴對方的結果——那何不同時啟動超過一條呢?

超純量(superscalar)處理器正是這麼做。它擁有多個執行單元——比方兩個整數 ALU、一個載入/儲存單元、一個浮點單元——每個週期取出並發射好幾條指令。現代核心寬度為 4 到 8,意味著每個時脈最多可啟動八條指令。CPI 不再卡在 1,而能降到 1 以下(我們通常把它倒過來,改稱 IPC,每週期指令數)。

但程式不是寫成一束束整齊的獨立指令。第 5 條指令常常需要第 3 條的結果。如果處理器嚴格依程式順序發射,一條卡住的指令就會擋住後面所有人——像一輛慢車堵住整條車道。解法是亂序執行(out-of-order execution):核心維持一池已解碼的指令(保留站/重排序緩衝區),執行任何輸入已就緒的指令,然後悄悄地依原本的程式順序退役(retire)結果,讓程式設計師完全看不到這番重新洗牌。

; Program order — instr 3 stalls on a cache miss
  I1: add  r1, r2, r3
  I2: mul  r4, r1, r5
  I3: load r6, [r7]      ; <-- misses cache, ~100+ cycles
  I4: sub  r8, r9, r10   ; independent of I3 — why wait?
  I5: xor  r11, r8, r12  ; depends on I4, not I3

In-order core:   I3 stalls -> I4, I5 stall too (lane blocked)
Out-of-order:    I4, I5 execute WHILE I3's load is in flight
                 results retire in order 1,2,3,4,5 -> program sees no change
亂序執行讓獨立的工作(I4、I5)在 I3 發生漫長[[ic-cache-memory|快取]]未命中期間先行進行,藏起順序核心無法藏起的延遲。

當物理走到盡頭:登納德縮放的終結與暗矽

2005 年前後,免費午餐被突然取消,元兇是功耗。登納德縮放曾承諾:縮小電晶體就能等比例降低其電壓,讓單位面積功率維持不變。但電壓只能降到一定程度,再低電晶體就無法乾淨地切換,而漏電流——即使電晶體「關閉」時仍在流動的電流——會爆炸性增長。大約在 90 奈米節點以下,臨界電壓停止縮放、漏電飆升,登納德縮放就此崩潰。一夜之間,把時脈拉高就意味著更多功率、更多熱量,卻無處可去。

Power ~= C * V^2 * f   (dynamic switching power)

  Dennard era:  shrink -> V down, C down  -> power/area constant
                so f could rise 'for free' each generation

  Post-2005:    V stuck (~0.7-1.0 V), leakage rises
                -> pushing f up just burns more watts as heat

  Thermal wall: ~100-150 W/cm^2 is about all you can cool
                in a cheap package -> clocks freeze near 3-4 GHz
動態功率與電壓的平方成正比。一旦電壓停止縮小,頻率便撞上熱牆。

然而摩爾定律並未在 2005 年停下——電晶體又持續縮小、翻倍了十來年。這造成了一道奇怪的新鴻溝。你能在晶粒上塞進比以往更多的電晶體,卻再也無法在熱預算內同時為它們全部供電。結果就是暗矽(dark silicon):在任一瞬間,晶片上有越來越大的比例必須閒置或以低電壓運行,因為把它全部點亮會超出散熱極限。

轉向:多核心取代單一快核心

如果你無法讓單一核心快很多,最直接的動作就是把好幾個放到同一塊晶粒上。多核心處理器正是如此:兩個、四個、八個,乃至數十個完整核心共用一個封裝,常常還共用最後一級快取與記憶體控制器。為何這能繞過功率牆?因為在高頻端,功率大致與頻率的三次方成正比(你還得提高電壓才能達到更高時脈)。兩個 2 GHz 的核心能提供比單一 4 GHz 核心更高的吞吐量,而且總功率更低——前提是你的工作能被切成兩塊平行運行。

最後那句但書才是陷阱,而且毫不留情。核心加倍不會讓速度加倍,因為幾乎沒有任何真實程式是完美平行的。那段必須循序執行的部分——讀取輸入、平行階段之間的記帳工作、最後的合併——就封死了一切上限。這就是阿姆達爾定律(Amdahl's law),值得用具體數字算一遍。

Amdahl's law:   Speedup(N) = 1 / ( (1 - p) + p/N )
   p = fraction of work that is parallelisable
   N = number of cores

Suppose p = 0.95 (95% parallel, 5% stubbornly serial):

   N = 2     Speedup = 1 / (0.05 + 0.95/2)   = 1.90x
   N = 8     Speedup = 1 / (0.05 + 0.95/8)   = 5.93x
   N = 32    Speedup = 1 / (0.05 + 0.95/32)  = 12.5x
   N = 1000  Speedup = 1 / (0.05 + 0.95/1000) = 19.5x
   N = inf   Speedup = 1 / 0.05               = 20x   <-- HARD CEILING

The 5% serial part alone caps you at 20x, no matter how many cores.
即使是 95% 平行的程式,加速也永遠無法超過 20 倍——隨著 N 增大,循序的殘餘部分主宰了一切。

新的頭痛問題:讓各核心的快取保持一致

多核心解決了功率問題,卻製造了正確性問題。在第三階你學到每個核心都有私有的 L1 快取——一份最近使用記憶體的高速本地副本。現在想像兩個核心,各自擁有自己的快取,都持有同一個變數 `x = 5` 的副本。核心 0 把 `x = 6` 寫進它的快取。核心 1 從它的過期快取讀取,仍看到 `5`。它們對現實的認知就此分歧。如果軟體無法信任所有核心都看到相同的記憶體,平行程式設計就變得不可能。

硬體的解法是快取一致性協定(cache-coherence protocol),最常見的是 MESI 的某種變體。每條快取線帶有一個狀態——Modified(已修改)、Exclusive(獨佔)、Shared(共享)或 Invalid(無效)——核心透過監聽共享匯流排或目錄來協調。當核心 0 想寫入 `x`,它必須先取得獨佔所有權,這會作廢其他每個核心的副本。核心 1 下一次讀取便會未命中,迫使它去抓取最新值。一致性是自動且對軟體不可見的,但並非免費:它耗費匯流排流量、能量與延遲。

MESI in action — two cores share variable x (starts: both Shared, x=5)

  Core0                       Core1
  ----                        ----
  read  x   -> S, x=5         read  x  -> S, x=5
  WRITE x=6 -> needs M
     |-- broadcast invalidate -->| line goes I (invalid)
     -> Core0 now M, x=6        | (copy thrown away)
                                read x -> MISS
     |<-- snoop, supply x=6 ----|  Core0 line -> S
                                -> Core1 S, x=6   (now consistent)

The invalidate + refetch is the hidden tax of every shared write.
一次寫入迫使廣播作廢;另一核心下次讀取便未命中並重新抓取。這種「一致性流量」正是為何高度共享的資料可能讓更多核心反而跑得**更慢**。

本階的意義:通往專用化之路

退一步,觀察跨越三個時代的策略轉變。最初,架構師用頻率買速度——直到熱牆擋住去路。接著用寬度與亂序執行的巧思(指令級平行)買速度——直到普通程式裡的平行性枯竭。然後用眾多核心(多核心)買速度——直到暗矽與阿姆達爾定律讓一模一樣的通用核心成為賠本生意。每一根槓桿,輪流撞上一堵牆。

  1. 頻率時代(約 1990–2005):乘著登納德縮放,提高時脈。終結於功率/熱牆。
  2. 指令級平行時代:加寬並亂序執行以壓低 CPI。終結於普通程式只暴露寥寥幾條獨立指令。
  3. 多核心時代(約 2005 起):複製核心以換取吞吐量。終結於阿姆達爾定律與暗矽。
  4. 專用化時代(當前):用領域專用加速器填滿暗矽,它們只做一件事,卻有遠遠更佳的每瓦效能。

那第四個時代正是後續幾階所在之處。當你無法為每個電晶體供電、也無法在通用程式裡找到更多平行性時,致勝之道就是不再通用。GPU、張量引擎、影像編解碼區塊——每一個都放棄彈性,去追求在狹窄任務上的龐大效率。你剛讀完的這段歷史,正是業界為何從「縮放一種大腦」轉向「打造一整座領域專用架構動物園」的原因。理解指令級平行與多核心撞上的那些牆,是理解「為何專用矽片是未來十年主導觀念」的先決條件。