Home
Explore Uedu
Student Console
Register as Member/Login
(2) In future presentations of the research findings, in addition to the course project website and public presentations, your real name and personal information will not appear in this research report. If you are interested in the research results, we can provide you with an executive summary after the study is completed.
問卷中心
Teacher Console
Course Setup
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

0

Local AI

--

CISOSE26 本地 AI UG26
政治大學 AQI 18 29°C

AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

CPU 與指令執行

CPU 與指令執行(進階):亂序、重新命名與 Tomasulo

從 ISA 與微架構的分野出發,拆解暫存器重新命名、保留站、重排序緩衝區與分支預測,看現代處理器如何「內部瘋狂亂序、外部嚴格有序」。

一條 add 指令到了現代 CPU 手裡,為什麼會被「拆開」再「重排」?

入門篇裡,我們把 CPU 想成一條乖巧的生產線:依程式計數器(PC, Program Counter)取出指令、解碼、執行,一條接一條,井然有序。這個畫面很適合建立直覺,卻和今天桌上型晶片內部真正發生的事相去甚遠。

真實情況更像一座吵雜的調度場:你寫下的指令一進處理器就被拆解成更細的微操作(micro-op)、貼上臨時編號、丟進一個共享的等待池,誰的輸入先備齊誰就先跑——跑完的順序甚至可能和你寫的順序完全不同。最後再有一個專門的單位負責「假裝一切都是照順序發生的」,把結果按程式原本的次序提交出去。

這篇進階文章要回答一個核心問題:現代 CPU 如何在「對外看起來嚴格照順序執行」與「對內瘋狂亂序、平行、推測」之間取得平衡? 我們會從指令集架構(ISA)與微架構的分野出發,一路走到暫存器重新命名(register renaming)、Tomasulo 演算法、重排序緩衝區(ROB),以及分支預測器的內部結構。

兩層抽象:ISA 與微架構

入門篇談的「機器語言」其實對應到一個正式名詞:指令集架構(ISA, Instruction Set Architecture)。它是硬體與軟體之間的契約,規定了有哪些指令、有幾個程式可見暫存器、記憶體如何定址、例外如何處理。x86-64、ARMv8(AArch64)、RISC-V 都是 ISA。

CPU 與指令執行進階概念示意圖

關鍵在於:ISA 只規定「行為」,不規定「怎麼做到」。同一套 x86-64 ISA,Intel 與 AMD 的實作天差地別;同一家公司不同世代的晶片實作也完全不同。這個「怎麼做到」的層次,叫做微架構(microarchitecture)

層次 規定什麼 範例
ISA 程式設計師看得到的行為契約 x86-64、ARMv8、RISC-V
微架構 硬體如何實現該契約 Intel Golden Cove、Apple Firestorm、AMD Zen 4

這個分野之所以重要,是因為它讓底下的工程師可以為所欲為:只要對外行為符合 ISA 契約,內部要拆指令、亂序、推測、快取都隨意。學習者要建立的核心心智模型是——ISA 是「程式語意」,微架構是「執行策略」,兩者解耦

CISC 的指令會先被翻成 micro-op

x86 是典型的複雜指令集(CISC),一條指令可能同時做「從記憶體讀、做運算、寫回記憶體」好幾件事。現代 x86 處理器並不直接執行這種複雜指令,而是在前端(front-end)先把它解碼成一條或多條精簡的微操作(micro-op / µop),這些 µop 長得很像精簡指令集(RISC)的單純指令。

x86 指令:        add  [rax], rbx     ; 把 rbx 加到記憶體 [rax] 的內容

解碼後的 µop:    load   t1 ← [rax]   ; 讀記憶體到臨時值
                add    t2 ← t1, rbx  ; 相加
                store  [rax] ← t2    ; 寫回記憶體

換句話說,現代「CISC」處理器的執行核心其實是一個 RISC 式的引擎。理解這點,後面所有亂序機制就都建立在「處理器真正調度的是 µop,不是你寫的指令」這個前提上。

真相位元:為什麼要「重新命名暫存器」

入門篇提過資料危障(data hazard):後一條指令需要前一條的結果。但還有一類危障更隱蔽,它根本不是真正的相依,而是因為暫存器名字不夠用造成的假相依。看這段:

(1) add  r1 ← r2, r3
(2) mul  r4 ← r1, r5     ; 真相依:要等 (1) 的 r1     —— RAW
(3) add  r1 ← r6, r7     ; r1 被重複使用,和 (1) 撞名 —— WAW
(4) sub  r8 ← r1, r9     ; 用的是 (3) 的 r1,不是 (1)  —— WAR / RAW
  • RAW(讀後寫,Read-After-Write):(2) 讀 (1) 寫的 r1。這是真相依(true dependency),無法消除,本質上資料就是要流過去。
  • WAW(寫後寫)WAR(寫後讀):(3) 又寫了 r1,純粹是因為這顆 ISA 只有 16 個架構暫存器(architectural register),編譯器被迫重複使用同一個名字。這兩者叫假相依(false / name dependency)——資料其實沒有流動關係,只是名字撞了。

解法是暫存器重新命名(register renaming):處理器內部其實有遠多於架構暫存器的實體暫存器(physical register)(現代核心常有 100~200 個以上)。每次有指令「寫」一個架構暫存器,就分配一個全新的實體暫存器給它,並更新一張對照表(Register Alias Table, RAT)。

重新命名後(p 開頭是實體暫存器):
(1) add  p10 ← p2, p3     ; r1 映射到 p10
(2) mul  p11 ← p10, p5    ; 讀 p10(真相依保留)
(3) add  p12 ← p6, p7     ; r1 改映射到 p12 —— 和 (1) 的 p10 完全無關了!
(4) sub  p13 ← p12, p9    ; 讀 p12

WAW 與 WAR 假相依瞬間消失,因為 (1) 和 (3) 現在寫的是不同的實體暫存器。這就是亂序執行能成立的關鍵前置處理:先把名字洗乾淨,剩下的只有真正不可違背的資料流。

Tomasulo 演算法:讓「準備好的」先跑

名字洗乾淨後,µop 被送進保留站(reservation station)——一個等待池。每個 µop 在站裡等它的運算元(operand)到齊;一旦兩個輸入都備妥、又有空閒的功能單元(functional unit),它就被發射(issue)出去執行,完全不管它在程式裡排第幾

這套機制源自 1967 年 IBM System/360 Model 91 的 Robert Tomasulo,至今仍是所有亂序處理器的骨架。它的精妙在於用一條共同資料匯流排(CDB, Common Data Bus) 廣播結果:任何功能單元算完,就把「我是 p11、我的值是 42」廣播到所有保留站;正在等 p11 的 µop 一聽到,立刻抓值、湊齊運算元、準備發射。

保留站快照(某個時脈週期):
┌────┬────────┬───────────┬───────────┐
│ 站 │ 操作   │ 運算元1    │ 運算元2    │
├────┼────────┼───────────┼───────────┤
│ RS1│ mul    │ p10 ✓ =7  │ p5  ✓ =2  │ ← 都到齊,可發射
│ RS2│ add    │ 等 p11    │ p9  ✓ =1  │ ← 卡在 p11,先等
│ RS3│ sub    │ p12 ✓ =5  │ p9  ✓ =1  │ ← 都到齊,可發射
└────┴────────┴───────────┴───────────┘
RS1 與 RS3 可同時發射到兩個 ALU;RS2 等 CDB 廣播 p11 後才動。

注意 RS3(程式中較後面的 sub)可以比 RS2(較前面的 add)先執行——這就是亂序執行(out-of-order execution) 的字面意思。處理器讓資料流(dataflow)決定執行順序,而非程式碼順序。

對外保持秩序:重排序緩衝區(ROB)

問題來了:如果指令亂序完成,萬一中間有一條觸發了例外(exception)、或某個分支預測錯了,怎麼辦?我們不能讓「程式順序上還沒輪到、卻已經搶先算完並改了記憶體」的指令造成不可逆的後果。

解法是重排序緩衝區(ROB, Reorder Buffer):一個 FIFO 佇列,µop 進入執行前按程式順序在 ROB 尾端登記一個位置,但完成順序任意。一條 µop 即使算完,也只是把結果暫放在自己的 ROB 條目裡,標記為「完成」但尚未提交

只有當一條 µop 走到 ROB 的最前端(head)、且確認它前面所有指令都已正確提交時,它才被退役(retire / commit)——這一刻它的結果才正式寫進架構狀態(程式可見暫存器、記憶體)。退役嚴格按程式順序進行。

亂序執行,順序退役:

執行完成的時間順序:  C  A  D  B   (亂)
                     ↓
ROB(按程式順序排隊): A → B → C → D
退役順序:             A  B  C  D   (照程式順序,一個都不能跳)

這個設計達成一個漂亮的雙重目標:內部極致亂序求效能,外部嚴格順序求正確。它也讓「精確例外(precise exception)」成為可能——若 B 發生分頁錯誤(page fault),處理器只要丟棄 ROB 中 B 之後(C、D)尚未退役的所有結果,狀態就乾淨地停在「A 已完成、B 出錯」,彷彿從未亂序過。

動手算一下:亂序到底快多少

考慮一段有長延遲載入的程式碼。假設 load 要 4 個週期(快取失誤更久),ALU 運算 1 個週期:

(1) load r1 ← [r2]      ; 4 週期
(2) add  r3 ← r1, r4    ; 真相依於 (1),必須等
(3) mul  r5 ← r6, r7    ; 與 (1)(2) 完全無關
(4) sub  r8 ← r6, r9    ; 與前面都無關

順序執行(in-order):嚴格照順序,(2) 卡住等 (1) 的 4 個週期時,(3)(4) 只能乾等。

週期:   1   2   3   4   5   6   7
(1)    [--- load 4 週期 ---]
(2)                        add
(3)                             mul
(4)                                  sub
總計約 7 個週期

亂序執行(out-of-order):(2) 在等 (1) 時,獨立的 (3)(4) 趁機填補空檔先跑:

週期:   1   2   3   4   5
(1)    [--- load 4 週期 ---]
(3)     mul
(4)         sub
(2)                        add
總計約 5 個週期

加速並非來自指令變快,而是來自把因相依而閒置的空檔(bubble)用獨立指令填滿。能填多滿,取決於程式裡有多少指令級平行度(ILP, Instruction-Level Parallelism)可挖。這也解釋了為何亂序窗口(ROB 大小)是現代核心的關鍵規格——窗口越大,越能往後看到更多獨立指令來填空。

分支預測:賭對了免費,賭錯了很貴

入門篇提過控制危障:在分支(branch)解出方向前,不知道下一條該取哪裡。亂序機器的管線又深又寬,一次預測失敗要清掉的 µop 多達數十條,懲罰極重。所以現代 CPU 的分支預測器(branch predictor) 做得極其精巧,準確率常達 95% 以上。

最基礎的構件是 2 位元飽和計數器(2-bit saturating counter)。它用四個狀態記錄「最近這個分支傾向跳或不跳」,避免單次反例就立刻翻盤:

狀態機(每個分支位址配一個計數器):

  強烈不跳 ⇄ 弱不跳 ⇄ 弱跳 ⇄ 強烈跳
   (00)      (01)    (10)    (11)
   預測:不跳         |  預測:跳

跳了 → 往右移一格(更傾向跳)
不跳 → 往左移一格(更傾向不跳)

關鍵設計:要連續猜錯兩次才會改變「跳/不跳」的預測方向。對一個跑 1000 次、只有最後一次跳出的迴圈,2 位元計數器全程只錯 1 次(最後跳出那次),遠優於「看上一次」的 1 位元預測器。

現代預測器更進一步引入歷史關聯:用一個全域歷史暫存器(GHR, Global History Register)記錄最近 N 個分支的實際走向,把這段歷史與分支位址雜湊後,去查一張型樣歷史表(PHT, Pattern History Table)。這樣即使是相關聯的分支(例如 if (x>0) ... if (x>0) ...)也能被準確預測。實務上的 TAGE、感知器(perceptron)預測器都屬此類進階設計。

預測對了,目標位址由分支目標緩衝區(BTB, Branch Target Buffer) 提供,管線無縫往下走;預測錯了,沿錯誤路徑推測執行(speculative execution)的 µop 全部從 ROB 中作廢,回到正確路徑重來——這就是入門篇提到的清空(flush)懲罰。

重點回顧

  • ISA 與微架構解耦:ISA 是對外的行為契約(x86、ARM、RISC-V),微架構是內部實作策略;同一 ISA 可有天差地別的實作。
  • 現代處理器把指令(尤其 CISC)拆成 µop 來調度,執行核心本質上是 RISC 式引擎。
  • 暫存器重新命名消除 WAW/WAR 假相依,把問題化約為不可違背的真資料流,是亂序執行的前提。
  • Tomasulo + 保留站 讓運算元到齊的 µop 先發射,由資料流而非程式順序決定執行次序;ROB 則保證退役嚴格按程式順序,達成「內部亂序、外部有序、例外精確」。
  • 分支預測器(2 位元飽和計數器、GHR+PHT、BTB)讓深管線在面對控制危障時仍能高吞吐運行,預測錯誤的代價是管線清空。

深入探討(研究所視角)

把上述機制串起來,現代高效能核心的真實面貌是:前端(取指、分支預測、解碼成 µop)→ 重新命名(RAT 分配實體暫存器)→ 亂序引擎(保留站、ROB、多個功能單元)→ 退役(按序提交)。值得在研究所層次延伸的幾個方向:

記憶體亂序與一致性(memory consistency)。 暫存器的相依靠重新命名解得很乾淨,但記憶體位址要到執行期才算得出來,使得 load/store 之間的相依變成模糊問題。處理器用載入/儲存佇列(load/store queue)記憶體相依預測(memory dependence prediction) 來推測「這個 load 是否與前面某個未完成的 store 撞址」,猜錯就得回滾。更深一層,不同 ISA 定義了不同的記憶體模型(memory model):x86 採較強的 TSO(Total Store Order),ARM/RISC-V 採較弱的鬆散順序(weak ordering),這直接影響多執行緒程式何時需要插入記憶體屏障(memory barrier / fence)。這是並行程式設計正確性的硬體根基,連結到 C++/Java 的記憶體模型與 std::atomic 的記憶體序語意。

超純量與 SMT。 入門篇提到超純量(superscalar)可在單週期發射多條指令;當單執行緒的 ILP 被榨乾、功能單元仍有空閒時,同時多執行緒(SMT, Simultaneous Multithreading;Intel 稱 Hyper-Threading) 讓兩個硬體執行緒共享同一套執行資源,用執行緒級平行(TLP)填補 ILP 留下的空檔。代價是共享資源(快取、保留站)的競爭,以及——如下——安全邊界的模糊。

推測執行的安全代價,再訪。 入門篇點到 Spectre/Meltdown。從本篇的機制看會更透徹:問題正出在「推測執行的 µop 即使最終在 ROB 被作廢、架構狀態完好回滾,它在快取等微架構結構留下的痕跡卻不會回滾」。攻擊者誘導處理器沿錯誤路徑推測載入機密資料、用它索引一塊陣列,再透過快取時序(cache timing)側通道把值還原出來。這揭示一個深刻的張力:ISA 契約只保證架構狀態的正確,卻從未承諾微架構狀態不洩漏資訊。後續的緩解(如推測載入硬化、站點隔離)與新研究方向(不可見推測, invisible speculation)都圍繞這條縫隙。理解這類議題的價值在於建立正確的系統設計與防禦觀念,相關探究應於合法、受控的研究環境中進行。

效能上限與 Amdahl 視角。 亂序窗口、發射寬度、預測準確率三者共同決定可達 IPC,但回報遞減:把 ROB 從 200 加到 400 條目,能多挖到的獨立指令越來越少(程式內在 ILP 有限)。這把效能戰場從「單核更寬更深」推向「多核 + 向量(SIMD) + 異質運算(GPU/NPU)」。你會發現入門篇結尾那句話再次應驗——「如何讓更多單元同時忙碌、又維持正確性」這個命題,從單條管線、到亂序窗口、到多核一致性、到資料中心級的分散式系統,只是換了尺度反覆登場。掌握本篇的 µop 調度與 ROB 退役模型,等於拿到了一把能貫穿所有尺度去思考「平行 vs. 正確」的鑰匙。

AI 共讀助教正在陪你讀:CPU 與指令執行(進階):亂序、重新命名與 Tomasulo
嗨!我是這篇文章的共讀助教,只根據〈CPU 與指令執行(進階):亂序、重新命名與 Tomasulo〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。