編譯器與程式碼產生

暫存器溢出(register spilling)

想像一張工作檯,一次只放得下少數幾樣工具。如果一個工作需要的工具多過放得下,你就得把一些放到身後的架子上,等再需要時取回。處理器的暫存器就是那張工作檯,堆疊就是那個架子。暫存器溢出就是當同時活躍的值多過暫存器時編譯器所做的事:它把一些值「溢出」到記憶體、稍後再載回。

溢出是暫存器配置內部的後備方案。當配置器無法把同時活躍的所有值都塞進可用的暫存器(程式有高暫存器壓力)時,它挑一些值來溢出:把它們存到堆疊上的一個槽位、釋放它們的暫存器給其他值,並在每個被溢出之值下次使用前插入一個載入把它取回。因此每個被溢出的值都多花兩個記憶體操作(一個儲存、一個載回),那是它若留在暫存器就不需要的。配置器盡量溢出最少被使用、或活躍範圍最長的值,以減少增加的記憶體流量。

它重要在於:溢出是純開銷——程式之所以做這份工作,只因為晶片用完了暫存器——而一個充滿溢出的熱迴圈,明顯比一個能塞進暫存器的慢。讀反組譯時,跡象是你真正計算的周圍,出現意料之外、來回於像 [rsp-8] 這類堆疊偏移的 mov 指令。一個誠實的提醒:溢出不是 bug,且常常無可避免;在固定的暫存器數量下,編譯器做的是對的事。對系統程式設計師的教訓更微妙——一次推太多活躍的值(巨大的函式、過度積極的內聯、太多同時存活的區域變數)會升高暫存器壓力、迫使溢出,所以有時活躍範圍較短的簡單程式碼,反而比壓垮暫存器檔的「最佳化」程式碼跑得快。

// 當活躍的值多於暫存器時,配置器輸出: mov [rsp-16], rbx ; 溢出:把一個活躍值停在堆疊上 ... ; 同時把 rbx 重用 mov rbx, [rsp-16] ; 在該值再次被需要前載回 // 這兩個記憶體操作之所以存在,只因為暫存器用完了

溢出碼就是當一個值無法全程留在暫存器時,配置器插入的儲存/載回成對指令。

溢出不是編譯器的 bug,而是暫存器有限造成的純開銷;實際可調的槓桿是暫存器壓力——較少同時活躍的值(較小的函式、較短的活躍範圍)意味著較少溢出,所以延長活躍範圍的「更多最佳化」可能適得其反。

又称
spillingspill coderegister pressure暫存器外溢