歡迎您光臨本站 註冊首頁

GCC編譯優化指南

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  前言

  網上關於編譯優化的文章很多,但大多零零散散,不成體系,本文試圖給出一個完整和清晰的優化思路,同時提供在實踐中如何進行優化的詳盡參考。但是,在介紹所有優化知識之前首先引用LFS-Book中的一句忠告:“使用編譯器優化得到的小幅度性能提升,與它帶來的風險相比微不足道”。你還要進行優化嗎?

 

  OK, crazy guy! Let's Go!!

  在繼續之前,作者還是奉勸各位:如果追求極致的優化,那麼它將是一件既耗時又麻煩的事情,你會陷入無止盡的測試、測試、再測試……另外 Gentoo wiki 上有這麼一句話:"GCC has well over a hundred individual optimization flags and it would be insane to try and describe them all."所以本文不會涉及全部GCC優化選項。最後作者還是再羅唆一句:優化應當適可而止為好,將精力留出來做一些其它事情會更有意義!

  先決條件

  本文的主要讀者是 LFS/Gentoo 的玩家,基本上比較 crazy 的玩家都接觸過,如果你之前從未使用過 LFS/Gentoo ,請先按照《Linux From Scratch 6.2 中文版》做一遍 LFS ,然後再來閱讀此文將會更有意義。另外,本文是建立在《深入理解軟體包的配置、編譯與安裝》一文基礎之上的,在開始閱讀本文之前,請先閱讀它。

  基本原理

  我們首先從三個方面來看與優化相關的內容:

  從運行時的依賴關係來看,對性能有較大影響的組件有 kernel 和 glibc ,雖然這嚴格說來這不屬於本文的話題,但是經過精心選擇、精心配置、精心編譯的內核與C庫將對提高系統的運行速度起著基礎性的作用。

  從被編譯的軟體包來看,每個軟體包的 configure 腳本都提供了許多配置選項,其中有許多選項是與性能息息相關的。比如,對於 Apache-2.2.6 而言,你可以使用 --enable-MODULE=static 將模塊靜態編譯進核心,使用 --disable-MODULE 禁用不需要的模塊,使用 --with-mpm=MPM 選擇一個高效的多路處理模塊,在不需要IPv6的情況下使用 --disable-ipv6 禁用IPv6支持,在不使用線程化的MPM時使用 --disable-threads 禁用線程支持,等等……這部分內容顯然不可能在本文中進行完整的講述,本文只能講述與優化相關的通用選項。針對特定的軟體包,請在編譯前使用 configure --help 查看所有選項,並精心選擇。

  從編譯過程自身來看,將源代碼編譯為二進位文件是在 Makefile 文件的指導下,由 make 程序調用一條條編譯命令完成的。而將源代碼編譯為二進位文件又需要經過以下四個步驟:預處理(cpp) → 編譯(gcc或g++) → 彙編(as) → 連接(ld) ;括弧中表示每個階段所使用的程序,它們分別屬於 GCC 和 Binutils 軟體包。顯然的,優化應當從編譯工具自身的選擇以及控制編譯工具的行為入手。

  大體上編譯優化就這"三板斧"(其實是"三腳貓")了,本文接下來的內容將討論這隻貓的后兩隻腳。

  編譯工具的選擇

  對於編譯工具自身的選擇,在假定使用 Binutils 和 GCC 以及 Make 的前提下,沒什麼好說的,基本上新版本都能帶來性能提升,同時比老版本對新硬體的支持更好,所以應當盡量選用新版本。不過追新也可能帶來系統的不穩定,這就要針對實際情況進行權衡了。本文以 Binutils-2.18 和 GCC-4.2.2/GCC-4.3.0 以及 Make-3.81 為例進行說明。

  configure 選項

  這裡我們只講解通用的"體系結構選項",由於"特性選項"在每個軟體包之間千差萬別,所以不可能在此處進行講解。

  這部分內容很簡單,並且其含義也是不言而喻的,下面只列出常用的值:

  i586-pc-linux-gnu

  i686-pc-linux-gnu

  x86_64-pc-linux-gnu

  powerpc-unknown-linux-gnu

  powerpc64-unknown-linux-gnu

  如果你實在不知道應當使用哪一個,那麼就乾脆不使用這幾個選項,讓 config.guess 腳本自己去猜吧,反正也挺準的。

  編譯選項

  讓我們先看看 Makefile 規則中的編譯命令通常是怎麼寫的。

  大多數軟體包遵守如下約定俗成的規範:

  #1,首先從源代碼生成目標文件(預處理,編譯,彙編),"-c"選項表示不執行鏈接步驟。

  $(CC) $(CPPFLAGS) $(CFLAGS) example.c   -c   -o example.o

  #2,然後將目標文件連接為最終的結果(連接),"-o"選項用於指定輸出文件的名字。

  $(CC) $(LDFLAGS) example.o   -o example

  #有一些軟體包一次完成四個步驟:

  $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) example.c   -o example

  當然也有少數軟體包不遵守這些約定俗成的規範,比如:

  #1,有些在命令行中漏掉應有的Makefile變數(注意:有些遺漏是故意的)

  $(CC) $(CFLAGS) example.c    -c   -o example.o

  $(CC) $(CPPFLAGS) example.c  -c   -o example.o

  $(CC) example.o   -o example

  $(CC) example.c   -o example

  #2,有些在命令行中增加了不必要的Makefile變數

  $(CC) $(CFLAGS) $(LDFLAGS) example.o   -o example

  $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) example.c   -c   -o example.o

  當然還有極個別軟體包完全是"胡來":亂用變數(增加不必要的又漏掉了應有的)者有之,不用$(CC)者有之,不一而足.....

  儘管將源代碼編譯為二進位文件的四個步驟由不同的程序(cpp,gcc/g++,as,ld)完成,但是事實上 cpp, as, ld 都是由 gcc/g++ 進行間接調用的。換句話說,控制了 gcc/g++ 就等於控制了所有四個步驟。從 Makefile 規則中的編譯命令可以看出,編譯工具的行為全靠 CC/CXX CPPFLAGS CFLAGS/CXXFLAGS LDFLAGS 這幾個變數在控制。當然理論上控制編譯工具行為的還應當有 AS ASFLAGS ARFLAGS 等變數,但是實踐中基本上沒有軟體包使用它們。

  那麼我們如何控制這些變數呢?一種簡易的做法是首先設置與這些 Makefile 變數同名的環境變數並將它們 export 為全局,然後運行 configure 腳本,大多數 configure 腳本會使用這同名的環境變數代替 Makefile 中的值。但是少數 configure 腳本並不這樣做(比如GCC-3.4.6和Binutils-2.16.1的腳本就不傳遞LDFLAGS),你必須手動編輯生成的 Makefile 文件,在其中尋找這些變數並修改它們的值,許多源碼包在每個子文件夾中都有 Makefile 文件,真是一件很累人的事!

  CC 與 CXX

  這是 C 與 C++ 編譯器命令。默認值一般是 "gcc" 與 "g++"。這個變數本來與優化沒有關係,但是有些人因為擔心軟體包不遵守那些約定俗成的規範,害怕自己苦心設置的 CFLAGS/CXXFLAGS/LDFLAGS 之類的變數被忽略了,而索性將原本應當放置在其它變數中的選項一股老兒塞到 CC 或 CXX 中,比如:CC="gcc -march=k8 -O2 -s"。這是一種怪異的用法,本文不提倡這種做法,而是提倡按照變數本來的含義使用變數。

  CPPFLAGS

  這是用於預處理階段的選項。不過能夠用於此變數的選項,看不出有哪個與優化相關。如果你實在想設一個,那就使用下面這兩個吧:

  -DNDEBUG

  "NDEBUG"是一個標準的 ANSI 宏,表示不進行調試編譯。

  -D_FILE_OFFSET_BITS=64

  大多數包使用這個來提供大文件(>2G)支持。

CFLAGS 與 CXXFLAGS

  CFLAGS 表示用於 C 編譯器的選項,CXXFLAGS 表示用於 C++ 編譯器的選項。這兩個變數實際上涵蓋了編譯和彙編兩個步驟。大多數程序和庫在編譯時默認的優化級別是"2"(使用"-O2"選項)並且帶有調試符號來編譯,也就是 CFLAGS="-O2 -g", CXXFLAGS=$CFLAGS 。事實上,"-O2"已經啟用絕大多數安全的優化選項了。另一方面,由於大部分選項可以同時用於這兩個變數,所以僅在最後講述只能用於其中一個變數的選項。[提醒]下面所列選項皆為非默認選項,你只要按需添加即可。

  先說說"-O3"在"-O2"基礎上增加的幾項:

  -finline-functions

  允許編譯器選擇某些簡單的函數在其被調用處展開,比較安全的選項,特別是在CPU二級緩存較大時建議使用。

  -funswitch-loops

  將循環體中不改變值的變數移動到循環體之外。

  -fgcse-after-reload

  為了清除多餘的溢出,在重載之後執行一個額外的載入消除步驟。

  另外:

  -fomit-frame-pointer

  對於不需要棧指針的函數就不在寄存器中保存指針,因此可以忽略存儲和檢索地址的代碼,同時對許多函數提供一個額外的寄存器。所有"-O"級別都打開它,但僅在調試器可以不依靠棧指針運行時才有效。在AMD64平台上此選項默認打開,但是在x86平台上則默認關閉。建議顯式的設置它。

  -falign-functions=N

  -falign-jumps=N

  -falign-loops=N

  -falign-labels=N

  這四個對齊選項在"-O2"中打開,其中的根據不同的平台N使用不同的默認值。如果你想指定不同於默認值的N,也可以單獨指定。比如,對於L2-cache>=1M的cpu而言,指定 -falign-functions=64 可能會獲得更好的性能。建議在指定了 -march 的時候不明確指定這裡的值。

  調試選項:

  -fprofile-arcs

  在使用這一選項編譯程序並運行它以創建包含每個代碼塊的執行次數的文件后,程序可以再次使用 -fbranch-probabilities 編譯,文件中的信息可以用來優化那些經常選取的分支。如果沒有這些信息,gcc將猜測哪個分支將被經常運行以進行優化。這類優化信息將會存放在一個以源文件為名字的並以".da"為後綴的文件中。

  全局選項:

  -pipe

  在編譯過程的不同階段之間使用管道而非臨時文件進行通信,可以加快編譯速度。建議使用。

  目錄選項:

  --sysroot=dir

  將dir作為邏輯根目錄。比如編譯器通常會在 /usr/include 和 /usr/lib 中搜索頭文件和庫,使用這個選項后將在 dir/usr/include 和 dir/usr/lib 目錄中搜索。如果使用這個選項的同時又使用了 -isysroot 選項,則此選項僅作用於庫文件的搜索路徑,而 -isysroot 選項將作用於頭文件的搜索路徑。這個選項與優化無關,但是在 CLFS 中有著神奇的作用。

  代碼生成選項:

  -fno-bounds-check

  關閉所有對數組訪問的邊界檢查。該選項將提高數組索引的性能,但當超出數組邊界時,可能會造成不可接受的行為。

  -freg-struct-return

  如果struct和union足夠小就通過寄存器返回,這將提高較小結構的效率。如果不夠小,無法容納在一個寄存器中,將使用內存返回。建議僅在完全使用GCC編譯的系統上才使用。

  -fpic

  生成可用於共享庫的位置獨立代碼。所有的內部定址均通過全局偏移表完成。要確定一個地址,需要將代碼自身的內存位置作為表中一項插入。該選項產生可以在共享庫中存放並從中載入的目標模塊。

  -fstack-check

  為防止程序棧溢出而進行必要的檢測,僅在多線程環境中運行時才可能需要它。

  -fvisibility=hidden

  設置默認的ELF鏡像中符號的可見性為隱藏。使用這個特性可以非常充分的提高連接和載入共享庫的性能,生成更加優化的代碼,提供近乎完美的API輸出和防止符號碰撞。我們強烈建議你在編譯任何共享庫的時候使用該選項。參見 -fvisibility-inlines-hidden 選項。

  硬體體系結構相關選項[僅僅針對x86與x86_64]:

  -march=cpu-type

  為特定的cpu-type編譯二進位代碼(不能在更低級別的cpu上運行)。Intel可以用:pentium2, pentium3(=pentium3m), pentium4(=pentium4m), pentium-m, prescott, nocona, core2(GCC-4.3新增) 。AMD可以用:k6-2(=k6-3), athlon(=athlon-tbird), athlon-xp(=athlon-mp), k8(=opteron=athlon64=athlon-fx)

  -mfpmath=sse

  P3和athlon-xp級別及以上的cpu支持"sse"標量浮點指令。僅建議在P4和K8以上級別的處理器上使用該選項。


[火星人 ] GCC編譯優化指南已經有350次圍觀

http://coctec.com/docs/program/show-post-71625.html