2011-03-29 109 views
3

有誰知道是否存在編譯器如何優化源代碼的列表?我比較喜歡GCC作爲例子。編寫代碼以幫助編譯器進行優化

我想知道程序員應該如何處理代碼以獲得良好的優化,並幫助編譯器優化它。程序員進行的一些優化可能會避免編譯器做更好的優化。

例子:

replace 
for (int i = 0; i < n - 1; i++) 
by 
int n2 = n - 1; 
for (int i = 0; i < n2; i++) 


for (int i = 0; i < n/2; i++) 
by 
int n2 = n/2; 
for (int i = 0; i < n2; i++) 



for (int i = 0; i < obj.calc_value(); i++) //calc_value() will return the same result with obj remaining unchanged. 
by 
int y = obj.calc_value() 
for (int i = 0; i < y; i++) 

保持代碼的簡單的閱讀和理解是很重要的。

感謝

編輯:

其他例子:

  • 內聯函數
  • 刪除遞歸
+0

編譯器也是人! :-)如果您的代碼易於閱讀和理解,他們會看到您的意圖併爲此生成代碼。如果你做了一些以前從未見過的「聰明伎倆」,你的同事和編譯器都不知道該怎麼做。 – 2011-03-29 05:10:16

回答

13

嚴重的是,剛剛離開,最多的編譯器。我已經看到了海灣合作委員會在其「瘋狂的」-O3水平上輸出的代碼,這證明了編寫這些優化引擎的人不是外星人,就是來自遙遠未來的時間。

我還沒有看到registerinline在我的代碼的性能有明顯的差異。這並不意味着不會,只是編譯器在處理器中提取最後一盎司的性能時比我們知道更多的技巧。

只要優化進行,它應該只在存在真正問題的地方完成。這意味着分析代碼並發現瓶頸,但更重要的是,不會優化在上下文中不被視爲緩慢的操作。對於用戶而言,一次操作是否需要十分之一秒或百分之一的零差異。

有時,優化可讀性是你可以做:-)


順便說一句,這只是一個的漂亮的花樣gcc在你的最好的之一。考慮下面的代碼應該計算階乘並返回它:

static int fact (unsigned int n) { 
    if (n == 0) return 1; 
    return n * fact (n-1); 
} 
int main (void) { 
    return fact (6); 
} 

這編譯爲(以-O3):

main: pushl %ebp   ; stack frame setup. 
     movl  $720, %eax  ; just load 720 (6!) into eax. 
     movl  %esp, %ebp  ; stack frame 
     popl  %ebp   ; tear-down. 
     ret      ; and return. 

這是正確的,GCC只是工作這一切在編譯時和原來整個事情到相當於:

int main (void) { return 720; } 

對比這與-O0(天真)版本:

main: pushl %ebp    ; stack 
     movl %esp, %ebp   ; frame 
     andl $-16, %esp   ; set 
     subl $16, %esp   ; up. 
     movl $6, (%esp)   ; pass 6 as parameter. 
     call fact    ; call factorial function. 
     leave      ; stack frame tear down. 
     ret      ; and exit. 

fact: pushl %ebp    ; stack 
     movl %esp, %ebp   ; frame 
     subl $24, %esp   ; set up. 
     cmpl $0, 8(%ebp)  ; passed param zero? 
     jne  .L2    ; no, keep going. 
     movl $1, %eax   ; yes, set return to 1. 
     jmp  .L3    ; goto return bit. 

.L2: movl 8(%ebp), %eax  ; get parameter. 
     subl $1, %eax   ; decrement. 
     movl %eax, (%esp)  ; pass that value to next level down. 
     call fact    ; call factorial function. 
     imull 8(%ebp), %eax  ; multiply return value by passed param. 

.L3: leave      ; stack frame tear down. 
     ret      ; and exit. 
+5

+1「優化可讀性是您可以做的最好的選擇」。我很想看到一些關於新編譯器所做優化類型的文檔。我已經閱讀了一些(循環展開,使用寄存器而不是堆棧,內聯,展開尾遞歸等),但這些都是明顯的和古老的技巧。 – 2011-03-29 03:09:23

+1

@Merlyn Morgan-Graham:我認爲這本書非常寶貴(如果你正在編寫這個編譯器)[高級編譯器設計實現](http://www.amazon.com/Advanced-Compiler-Design-Implementation-Muchnick/dp/ 1558603204/ref = pd_rhf_shvl_3)但是,自從我買了這款產品以來,我一直都這麼想,所以我確信現在有更多的書。 – 2011-03-29 04:31:20

+0

所以我想它可以從循環條件中優化'n-1'? :-) – 2011-03-29 06:29:44

3

所有建議的改進都是Loop-invariant code motion的示例,這是幾乎所有優化編譯器都做的基本優化。

實際編譯器執行的優化範圍比這些示例要先進得多。上面鏈接的維基百科文章有一些鏈接供進一步閱讀。

+0

+1用於識別和解決他詢問的具體優化。 – 2011-03-29 08:16:38

0

至於你發佈的密碼,我同意paxdiablo的回答。編譯器在很多情況下可以很好地優化。

模板元編程

如果你正在尋找幫助編譯器優化(並有一個理由 - 型材,異型材,型材),最可能有用的技巧我見過的template metaprogramming

Boost has some direct support for template metaprogramming

一個有用的例子是模板元編程的矩陣數學庫,它減少了完成操作的次數,同時將這些操作留在源代碼中。他們還在編譯時完全評估一些操作。

這裏的第一個那些顯示在谷歌的:http://arma.sourceforge.net/

const正確性

您應該調查,因爲它可以很容易地幫助錯誤證明你的代碼的另一件事,是const-correctness。另外一個好處是,it can sometimes help with compiler optimizations

const-correctness和template-metaprogramming都很難掌握,但非常有用。這是大多數的C++ :)

0

無論什麼「不優化」在這裏告訴你,它總是可以幫助(在我的情況下C)編譯器產生更好的代碼。

編譯器會比大多數程序員在策略性地訂購代碼時更好,以使可用管道和執行單元達到最佳使用狀態。話雖如此,但重要的是要指出,編譯器在戰略編程層面上並不擅長,即高於單個程序的層次。

表現不佳的軟件遠比表現良好。 「不優化者」很少承認的一件事(並且絕對不在你的文章的答案中)是因爲編寫低性能軟件(顯然)是可能的,所以編寫運行良好的軟件也顯然是可能的。編譯器不是鍊金術:給它們提供垃圾源代碼,它們將產生垃圾機器代碼。喂他們有能力的源代碼,他們一定會產生有能力的機器代碼。

我對優化主題很矛盾。一方面,如果已經確定了性能問題(通常在開發即將完成時),通常來不及得到任何顯着改進(例如100%或更高),因爲這需要戰略級別的工作(沒有的時間)。另一方面,如果提前完成「完整的性能圖」,那麼「不優化器」會說現在還沒有進行優化,因爲沒有有效的數據。優化往往是在恐慌中完成的,以通過驗收測試來挽救一些結構不良的軟件或者作爲生命支持。在一個積極的環境中進行優化(使得代碼更好地執行)或多或少沒有被聽到(至少在這裏?)。

我喜歡編寫代碼表現良好的挑戰。我的代碼是可讀的,但從第一手的經驗我選擇某些代碼結構,因爲我知道他們會表現更好。另外,我從開發的一開始就對我的應用程序進行測試,以便測量自己,然後隨着開發的進展,我可以關注性能。