連結器、載入器與目的檔格式

連結期最佳化(link-time optimization)

/ LTO, said by its letters /

通常編譯器一次最佳化一個原始檔,然後連結器只是把完成的片段黏在一起、不往裡面看。這表示橫跨兩個檔案的最佳化——譬如把 a.c 裡的小函式內聯進 b.c 的熱迴圈,或刪掉沒人真正呼叫的函式——根本無法發生,因為在編譯時兩個檔案都看不到對方。連結期最佳化移除了那道牆:它把繁重的最佳化延後到連結時,那時整個程式終於能一次被看見。

機制在於巧妙地改變 .o 檔含有的東西。啟用 LTO(用 -flto 編譯)後,編譯器不把最終機器碼放進每個 .o;它改為儲存其內部中介表示(IR)——程式意義的序列化形式。連結時,懂 LTO 的連結器(或外掛)蒐集所有那些 IR 區塊、把它們當成一個大程式、跨整個東西執行最佳化器,「然後」才產生機器碼。如今跨檔案內聯、全程式無用程式碼消除、更好的常數傳播都變得可能。有兩種風味。完整(或「fat」)LTO 把所有東西載入記憶體、當成單一龐然整體來最佳化——最大化的最佳化,但在大型程式上慢且耗記憶體。Thin LTO 做一次便宜的全域分析以決定把什麼匯入哪裡,然後只帶相關匯入地平行最佳化每個模組——實務上幾乎一樣好、快很多、可平行,這就是它在大型程式碼庫中被偏好的原因。

它重要,是因為 LTO 往往以建構時間為代價,免費換來真實的速度與大小收益,尤其對拆散在許多小函式與檔案中的程式碼。誠實的告誡:建構變慢、用更多記憶體,除錯資訊與建構可重現性可能受損,而且因為最佳化器現在跨整個程式看,原本在單一檔案內碰巧無害的潛伏未定義行為,可能在最佳化器以新的、更寬的視野利用它時突然咬人。LTO 不會把錯誤的程式碼變正確——它可能讓既有臭蟲更明顯。

$ gcc -O2 -flto -c a.c b.c # .o 檔放 IR,不是最終程式碼 $ gcc -O2 -flto a.o b.o -o app # 連結時做全程式最佳化 # thin LTO(clang):-flto=thin -> 平行、快;full:-flto=full

用 -flto 時 .o 檔儲存中介表示;最佳化器在連結時跨整個程式執行,使跨檔案內聯成為可能。

LTO 不修正臭蟲;藉由拓寬最佳化器的視野,它可能「揭露」先前在單一檔案內被遮蔽的潛伏未定義行為。Thin LTO 以一點頂峰最佳化換取快得多、可平行的建構,通常是大型專案的正確預設。

又稱
LTOwhole-program optimizationthin LTO全程式最佳化