程式設計師的 CPU 微架構

亂序執行與暫存器更名(out-of-order execution and register renaming)

想像一位廚師拿到一張按順序排好的工作清單,其中第 3 項是「等烤箱」,而第 4、5 項是可以同時做的、彼此獨立的切菜。笨廚師站在烤箱前發呆;聰明的廚師趁等的時候切菜,再回頭顧烤箱。亂序執行就是中央處理器當那個聰明廚師:它不嚴格按程式順序執行指令,而是一旦某條指令的輸入就緒就執行它,同時把一條停滯的指令(比方說在等一次 DRAM 載入的那條)擱到一旁。

核心問題是程式順序製造了假相依。若指令 A 寫入暫存器 rax,而稍後一條不相干的指令 B 也寫入 rax,那麼用同一個實體暫存器時 B 就不能搶在 A 前面跑——即使它們不共用任何真實資料、只共用 rax 這個名字。暫存器更名化解了這點:中央處理器擁有的實體暫存器,遠多於指令集所暴露的那幾個架構名稱(rax、rbx 等等),它把每一次對某架構暫存器的「寫入」對映到一個全新的實體暫存器。如此一來 A 與 B 寫的是不同的實體暫存器,可以任意順序執行。再配上一個盯著哪些運算元已就緒的排程器,核心便以非程式順序執行指令,靠在停滯周圍跑獨立工作來把停滯排掉。關鍵在於,結果仍以原本的程式順序「退役」(retire,即變得在架構上可見),所以程式行為和寫的一模一樣。

這為何是現代效能的引擎:單單一次 DRAM 失誤就要花數百個週期,而亂序執行讓中央處理器在那段等待中持續做有用的、獨立的工作,而非凍結——它隱藏了記憶體延遲。它能往前看的指令視窗是有限的(受重排緩衝器所限),所以它無法隱藏無限的延遲,長相依鏈仍會序列化。但這套在你原始碼裡看不見的機制,正是為何同樣的指令在一個寬的亂序核心上能比一個簡單的循序(in-order)核心快上二到十倍。

x = arr[i]; /* 失誤到 DRAM,300 週期 */ y = a + b; z = c * d; 那兩個算術運算不相依於 x,所以亂序核心把它們的輸出更名並在那次長載入期間執行它們,等 x 終於到達後再依序退役這三條。

經典的勝利:有用的算術在一次長記憶體停滯的陰影下執行,而非乾等它。

亂序是一種對你程式可觀察結果隱形的硬體重排——但它並不放寬記憶體模型。跨執行緒的順序仍需原子操作與屏障;亂序永遠不會讓一場資料競爭變安全。

又称
OoO executiondynamic schedulingregister renaming亂序執行OoO