編譯器優化過程具體是做了些什麼,優化後的程序速度能提高多少百分比?
題主問的「編譯器優化」有點指代不明啊,如果「編譯器優化」是指對編譯器自身的優化,那麼Bootstrap(中文譯為自舉?)可能是一個例子。
但我覺得題主問得更可能是編譯器對程序的優化,類似於gcc -o3這樣子的,試著舉幾個簡單的課堂例子,用C語言代碼給出例子幫助理解(實際上應該用IR),拋磚引玉了。高階的還請藍色大大和RednaxelaFX 大大補充了。
編譯器對程序的優化,大體上(我知道的)有這樣幾個原則/思路:
編譯器優化原則之一:能在編譯階段(compile time)做的工作,就不要在程序運行時(runtime)做。
例1: Constant Propagation (常數傳遞?) Constant Folding (中文常數摺疊?)
int freq = 32768;
int secs = 15;
int ticks = freq * secs; //假設後面freq和secs兩個變數沒有別重新賦值
Constant Propagation 優化後:(實際的優化應該在Intermediate Representation里,這裡為了方便理解還是用C語言了)
int ticks = 32768*15;
Constant Folding 優化後:
int ticks = 491520; //編譯器將常數運算結果計算後賦值
這個例子很簡單,相信大家都能看懂。
程序優化前,有3個變數需要3個寄存器,一次乘法運算。
程序優化後,只有1個變數需要一個寄存器,沒有乘法運算。這個優化看起來很微不足道,但實際上用途很廣。為了程序的可讀性和可維護性,大多數程序員應該還是會選用第一種方式寫3行程序而不是直接甩下一行int ticks = 491520讓後來讀程序的人摸不到頭腦。有了編譯器的優化,程序員既可以寫出易讀的程序又不必擔心性能受影響。尤其是在嵌入式領域,很多低端晶元根本就沒有硬體乘法器,如果程序不做上述優化可能這3行代碼需要幾十個cycle,優化過後一個cycle就搞定,你說性能差了多少? 而付出的代價,只是編譯程序的時間稍微長了那麼一點點,who cares?
類似的優化:Dead code elimination, Common subexpression elimination等
編譯器優化原則之二:對循環優化,優化,再優化!
例2:Loop-invariant code motion (例子來自Wiki)for (int i = 0; i &< n; i++) {
x = y + z;
a[i] = 6 * i + x * x;
}
很顯然這個循環里,x這個變數被毫無疑義地賦值了n次,這一步驟完全可以在循環之前做。
優化後:x = y + z;
t1 = x * x;
for (int i = 0; i &< n; i++) {
a[i] = 6 * i + t1;
}
100個循環,每個循環少執行了一條語句,就相當於是少執行了100條語句啊。要是循環1000次,1000次呢?有的模擬程序都是幾十上百萬次的循環,手賤多寫了一條這樣的語句你說對程序性能影響多大?你說你技術高超不會寫出這樣的程序,但你能保證所有人都不這樣寫嗎?
例3:Loop Unrolling (循環展開)int array[1024] = ...
for (int i = 0; i &< 1024; i++) {
array[i] *= 2;
}
優化後:
int array[1024] = ...
for (int i = 0; i &< 1024; i+=4) {
array[i] *=2;
array[i+1] *=2;
array[i+2] *=2;
array[i+3] *=2;
}
看起來程序優化之後比以前還長了。但實際上,每執行一次循環,都要有一次條件的判斷(i&<1024)。如果不做優化,那麼循環1024次,就要對條件判斷1024次。優化後,只循環256次,那麼判斷的次數就減少到256次。而且這個優化可以將程序並行化,在運算資源充足的時候可以大幅提高運算速度和效率。當然代價是編譯後代碼Size變大。
類似的優化:Software Pipelining, Strength Reduction等等。
之所以對Loop進行著重優化,是因為有統計表明世界上的程序大部分時間都是跑在loop里的。loop裡面優化了一小點,就會對整個程序的性能有很大提升。
最後給一個實際應用的例子:
最近在用某廠DSP晶元做項目,搞DSP的同學都知道DSP上的硬體浮點運算資源非常豐富,但因為程序是從別處扒來的沒有針對DSP進行優化,編譯器又比較蠢,所以DSP並沒有發揮出實際性能。然後我們手動展開了幾個比較核心的loop,這樣運算資源得到充分利用,程序性能提升了3倍。嗯,就做了這一件事兒,性能提升了3倍。懂一點編譯器的知識就是好~利益相關:不是搞編譯器的,各位輕拍。
總的來說可以分成幾個方向,臨時想的,可能不太全面。
一個是冗餘的刪除,比如死代碼刪除,常量或者複寫傳播,公共子表達式刪除等等
一個是計算強度削弱,包含但不僅限於一些基於邏輯或者算術表達式的優化,例如把乘法轉化成一系列的位移和加減法的組合等
一個是擴大優化器的作用範圍的優化,例如內聯,基本塊合併等等,基本目的在於獲得更大的優化目標
再有就是體系相關的優化,簡單如指令選擇時的一些窺孔性質的指令合併,複雜如寄存器分配,指令調度等np問題的解決。
最後,我想把循環優化單獨歸為一類,這些優化目的錯綜複雜,既有基於強度削弱的循環不變式外提,也有為了獲得更大的循環體以做其他優化的循環展開,還有在此之上來做體系相關優化的向量化軟流水等等
寫完後感覺我這樣分類不太合理…… 先湊合吧好像自己刷一遍 15-745 Optimizing Compilers for Modern Architectures, Spring 2016 是比在這提問好得多的方式。。
可恥的匿了昨天剛好碰到一個很有意思的場景,和這位答主探討了下,然後編譯器給出的答案真的驚艷到我了編譯器能否對如下場景優化,以及如何檢查不同編譯器對此是否做了優化? - 左方園的回答 - 知乎
像Haskell的編譯器,開了優化選項,能把指數級的複雜度優化成多項式…
優秀程序員讓編譯器無事可做:)
o0和o3差別可是相當的大
深入理解Java虛擬機:JVM高級特性與最佳實踐 第2版 高清PDF+源碼_Linux下載_Linux公社-Linux系統門戶網站第十章
推薦閱讀:
※深度學習如何影響運籌學?
※優化器優化編譯器,然後優化後的編譯器又重新編譯優化器,一直循環達到最優?
※新到一家P2P公司在寫網站推廣方案,由於沒有寫過,希望大家給些補充和意見?謝謝