跳轉到

1991 - 分支預測:推測執行的鑰匙#

案件摘要#

1991 年,Tse-Yu Yeh 與 Yale Patt 發表 two-level adaptive branch prediction: 以「分支歷史 × 模式歷史表」讓預測準確率突破 90%,後續演進至 99%+。 分支是流水線的天敵——每 5–6 條指令就一個分支,預測錯了整條流水線作廢。 分支預測讓推測執行(speculative execution)成為可能,現代 CPU 的性能自此起飛。

前因 -- 為什麼會有這個案子#

  • 流水線的分支代價:分支方向要到執行後段才知道,流水線越深、mispredict penalty 越大:
\[ \text{penalty} = O(\text{pipeline depth}),\qquad \text{每 } 5\text{–}6 \text{ 條指令一個分支} \]
  • 早期預測太笨:static(總是預測不跳)準確率約 60–70%;1-bit bimodal 在迴圈出口連錯兩次。
  • Tomasulo(1967)已備好引擎:亂序執行能榨 ILP,但分支方向未定時不能推測——差一把鑰匙。
  • 關鍵推理:分支行為有規律(迴圈、錯誤處理),規律就能被學習——用歷史表記住每個分支的「脾氣」:
\[ \text{預測} = f(\underbrace{\text{分支位址}}_{\text{哪個分支}} \times \underbrace{\text{歷史模式}}_{\text{最近行為}}) \]

線索與推理 -- 數學式、程式、理論#

1. 案件現場:預測器演進譜系#

演算法 年代 準確率 原理
static 1970s 60–70% 編譯期決定(向後跳=預測跳)
1-bit bimodal 1980s 85–90% 每分支一個 bit(上次的結果)
2-bit 飽和計數器 1980s 90% 兩次錯才改變預測方向
two-level adaptive 1991 93–97% 歷史模式 × 模式表
gshare/TAGE 1990s– 97–99%+ 全域歷史雜湊/標籤式幾何歷史

2. 2-bit 飽和計數器:狀態機的智慧#

每個分支配一個 2-bit 計數器,四個狀態:

\[ \text{ST(強跳)} \rightleftarrows \text{WT(弱跳)} \rightleftarrows \text{WN(弱不跳)} \rightleftarrows \text{SN(強不跳)} \]
  • 預測跳躍當計數器在 ST/WT;執行結果跳躍則計數器加一(飽和到 ST),不跳則減一(飽和到 SN)。
  • 兩次錯才轉向:迴圈出口要連錯兩次才放棄「跳」的預測——比 1-bit 在長迴圈中少一半錯誤。

3. Two-level adaptive:歷史的歷史#

Yeh–Patt 的洞見:分支的行為與最近的執行路徑相關——

\[ \text{PHT}[\underbrace{\text{BHR}}_{\text{最近 } k \text{ 次結果}}] \to \text{2-bit 計數器} \]
  • BHR(branch history register):記錄最近 \(k\) 次分支結果的模式(如 0101)。
  • PHT(pattern history table):每種模式對應一個 2-bit 計數器——同一分支在不同模式下「脾氣」不同。
  • 例:if (i < 100) 在正常迭代時總是跳、在最後一次不跳——模式表學會分別對待。

4. BTB 與推測執行:鑰匙插進鎖裡#

  • BTB(branch target buffer):記住分支的目標位址,預測階段就知道要跳去哪。
  • 推測執行:預測後立刻沿預測路徑執行,mispredict 才回復(ROB 保證精確復原,見 1967-Tomasulo 案):
\[ \text{IPC} = \frac{1}{1 + p \times \text{penalty}} \quad\xrightarrow{\text{準確率 99\%}}\quad \text{流水線幾乎滿載} \]
  • 代價:推測執行的錯誤路徑會留下痕跡(cache touch)——2018 年 Spectre/Meltdown 攻擊正是從這裡偷資料。

5. Python 實作:預測器準確率比較#

def predict_static(seq):                 # 總是預測「不跳」
    return sum(1 for b in seq if b == 0) / len(seq)

def predict_bimodal1(seq):
    last, ok = 0, 0                      # 初始預測不跳
    for b in seq:
        ok += (last == b)
        last = b
    return ok / len(seq)

def predict_bimodal2(seq):
    ctr, ok = 1, 0                       # 初始弱不跳
    for b in seq:
        pred = 1 if ctr >= 2 else 0
        ok += (pred == b)
        ctr = min(3, ctr + 1) if b else max(0, ctr - 1)
    return ok / len(seq)

def predict_two_level(seq, k=4):         # Yeh–Patt two-level adaptive
    bhr, pht, ok = 0, {}, 0
    for b in seq:
        pred = (pht.get(bhr, 1) >= 2)
        ok += (pred == b)
        c = pht.get(bhr, 1)
        pht[bhr] = min(3, c + 1) if b else max(0, c - 1)
        bhr = ((bhr << 1) | b) & ((1 << k) - 1)
    return ok / len(seq)

seq = [1]*9 + [0] + [1]*9 + [0] + [1]*9 + [0] + [1]*9 + [0]   # 迴圈:9跳1不跳
for name, fn in [('static', predict_static), ('1-bit', predict_bimodal1),
                 ('2-bit', predict_bimodal2), ('two-level', predict_two_level)]:
    print(f"{name:10s} 準確率 = {fn(seq):.0%}")
# static    準確率 = 80%
# 1-bit     準確率 = 69%   ← 每次迴圈出口與入口都錯
# 2-bit     準確率 = 80%   ← 出口只錯一次
# two-level 準確率 = 89%   ← 模式表學會迴圈長度

結案 -- 後果與影響#

  • 推測執行成為標準:Pentium Pro(1995)、PowerPC 604、Alpha 21264——分支預測 + 亂序執行 = 現代 CPU 三本柱。
  • 準確率軍備競賽:gshare(1993)、TAGE(2010s)把準確率推到 99%+,深流水線(20+ 級)才敢建。
  • Spectre/Meltdown(2018):推測執行的 side-channel 教訓——性能的鑰匙也開了安全的後門。
  • 理論延續:ML 分支預測(感知器預測器 2001)、ML 引擎預測——源頭都在 1991 這篇論文。

關鍵人物與文獻#

  • Tse-Yu Yeh、Yale Patt:two-level adaptive branch prediction, ISCA 1991。
  • James Smith:2-bit 飽和計數器(1981)。
  • Scott McFarling:gshare 併合預測(1993)。
  • André Seznec:TAGE 標籤式幾何歷史預測器(2006–2010s)。
  • 相關案件:1967-Tomasulo演算法.md、2002-Simultaneous多執行緒.md、2007-多核心CPU.md。