指令排程(instruction scheduling)
現代處理器不會等一個指令做完才開始下一個;它在一條叫管線(pipeline)的生產線上把它們重疊,甚至能同時啟動好幾個。但這只有在下一個指令不是在等一個尚未就緒的結果時才行得通。指令排程就是後端重排指令、讓處理器在緩慢的結果還在路上時有獨立的工作可做、使管線保持忙碌而非停頓的步驟。
重排必須尊重相依性:如果指令 B 使用指令 A 的結果,B 就不能移到 A 之前。排程器建立指令的相依圖,然後選一個能隱藏延遲的合法順序。經典案例是載入:一個來自記憶體的值可能要許多週期才抵達,立刻使用它的指令會停頓等待。排程器把獨立的指令移到中間,使處理器在等待期間做有用的工作。它也考慮晶片有哪些執行單元(以免把所有工作堆到一個單元)以及每個指令的延遲。這一切都在保留程式意義的前提下進行——只有獨立操作之間的順序改變。
它重要在於:在深度管線化的處理器上,良好的排程可能是「不斷停頓的迴圈」與「接近尖峰吞吐量的迴圈」之間的差別。它也是後端要依微架構調校的原因之一。兩個誠實的提醒。第一,現代亂序處理器無論如何都會在執行期以硬體重排指令,所以編譯器的靜態排程在較簡單的循序晶片上、以及作為「給硬體好原料」的方式時最重要;別誇大它在大型亂序核心上的效果。第二,這是編譯器為效能重排指令,它之所以被允許,是因為重排保留了「單執行緒可觀察行為」——但同樣的自由正是為什麼「其他」執行緒看到的記憶體操作可能顯得被重排,那是記憶體模型的範疇,不是單執行緒排程承諾要保留的。
; 排程前(停頓等待緩慢的載入): mov rax, [rbx] ; 載入:結果許多週期內不就緒 add rax, 1 ; 停頓:現在就需要 rax mov rcx, rdx ; 獨立工作,卡在停頓後面閒置 ; 排程後(用獨立工作填補等待): mov rax, [rbx] mov rcx, rdx ; 上移,在載入進行中執行 add rax, 1
獨立工作被移進空檔,使處理器在載入結果仍在抵達時保持忙碌。
靜態排程在循序晶片上最重要;大型亂序核心無論如何都會以硬體重排。而它雖保留單執行緒可觀察行為,同樣的重排自由正是為什麼其他執行緒能看到亂序的記憶體操作——那是記憶體模型的工作,不是排程器的承諾。