天天看點

gcc -O0 -O1 -O2 -O3 四級優化選項及每級分别做什麼優化(轉)

Gcc 編譯優化簡介 gcc 提供了為了滿足使用者不同程度的的優化需要,提供了近百種優化選項,用來對{編譯時間,目标檔案長度,執行效率}這個三維模型進行不同的取舍和平衡。優化的方法不一而足,總體上将有以下幾類:1)精簡操作指令;2)盡量滿足cpu的流水操作;3)通過對程式行為地猜測,重新調整代碼的執行順序;4)充分使用寄存器;5)對簡單的調用進行展開等等。想全部了解這些編譯選項,并在其中挑選适合的選項進行優化,無疑像個噩夢般的過程。單從gnu的官方網站上得到的手冊來看,描述依然比較蒼白,不足以完全了解選項的使用範圍和原理。(GCC has well over a hundred individual optimization flags and it would be insane to try and describe them all) 

幸而gcc提供了從O0-O3以及Os這幾種不同的優化級别供大家選擇,在這些選項中,包含了大部分有效的編譯優化選項,并且可以在這個基礎上,對某些選項進行屏蔽或添加,進而大大降低了使用的難度,畢竟,在一定基礎上進行取舍,比萬事從頭開始要好得多。下面着重圍繞這幾個不同的級别進行簡單介紹。(由于gcc不同版本手冊差異比較大,以下主要以gcc-3.4.6為參考) 

-O0: 不做任何優化,這是預設的編譯選項。 

-O和-O1: 對程式做部分編譯優化,對于大函數,優化編譯占用稍微多的時間和相當大的記憶體。使用本項優化,編譯器會嘗試減小生成代碼的尺寸,以及縮短執行時間,但并不執行需要占用大量編譯時間的優化。 

打開的優化選項: 

l -fdefer-pop:延遲棧的彈出時間。當完成一個函數調用,參數并不馬上從棧中彈出,而是在多個函數被調用後,一次性彈出。 

l -fmerge-constants:嘗試橫跨編譯單元合并同樣的常量(string constants and floating point constants) 

l -fthread-jumps:如果某個跳轉分支的目的地存在另一個條件比較,而且該條件比較包含在前一個比較語句之内,那麼執行本項優化.根據條件是true或者false,前面那條分支重定向到第二條分支的目的地或者緊跟在第二條分支後面. 

l -floop-optimize:執行循環優化,将常量表達式從循環中移除,簡化判斷循環的條件,并且optionally do strength-reduction,或者将循環打開等。在大型複雜的循環中,這種優化比較顯著。 

l -fif-conversion:嘗試将條件跳轉轉換為等價的無分支型式。優化實作方式包括條件移動,min,max,設定标志,以及abs指令,以及一些算術技巧等。  

l -fif-conversion2基本意義相同,沒有找到更多的解釋。 

l -fdelayed-branch:這種技術試圖根據指令周期時間重新安排指令。 它還試圖把盡可能多的指令移動到條件分支前, 以便最充分的利用處理器的治理緩存。 

l -fguess-branch-probability:當沒有可用的profiling feedback或__builtin_expect時,編譯器采用随機模式猜測分支被執行的可能性,并移動對應彙編代碼的位置,這有可能導緻不同的編譯器會編譯出迥然不同的目标代碼。 

l -fcprop-registers:因為在函數中把寄存器配置設定給變量, 是以編譯器執行第二次檢查以便減少排程依賴性(兩個段要求使用相同的寄存器)并且删除不必要的寄存器複制操作。 

-O2: 是比O1更進階的選項,進行更多的優化。Gcc将執行幾乎所有的不包含時間和空間折中的優化。當設定O2選項時,編譯器并不進行循環打開()loop unrolling以及函數内聯。與O1比較而言,O2優化增加了編譯時間的基礎上,提高了生成代碼的執行效率。 

O2打開所有的O1選項,并打開以下選項: 

l -fforce-mem:在做算術操作前,強制将記憶體資料copy到寄存器中以後再執行。這會使所有的記憶體引用潛在的共同表達式,進而産出更高效的代碼,當沒有共同的子表達式時,指令合并将排出個别的寄存器載入。這種優化對于隻涉及單一指令的變量, 這樣也許不會有很大的優化效果. 但是對于再很多指令(必須數學操作)中都涉及到的變量來說, 這會時很顯著的優化, 因為和通路記憶體中的值相比 ,處理器通路寄存器中的值要快的多。 

l -foptimize-sibling-calls:優化相關的以及末尾遞歸的調用。通常, 遞歸的函數調用可以被展開為一系列一般的指令, 而不是使用分支。 這樣處理器的指令緩存能夠加載展開的指令并且處理他們, 和指令保持為需要分支操作的單獨函數調用相比, 這樣更快。 

l -fstrength-reduce:這種優化技術對循環執行優化并且删除疊代變量。 疊代變量是捆綁到循環計數器的變量, 比如使用變量, 然後使用循環計數器變量執行數學操作的for-next循環。 

l -fcse-follow-jumps:在公用子表達式消元時,當目标跳轉不會被其他路徑可達,則掃描整個的跳轉表達式。例如,當公用子表達式消元時遇到if...else...語句時,當條為false時,那麼公用子表達式消元會跟随着跳轉。   

l -fcse-skip-blocks:與-fcse-follow-jumps類似,不同的是,根據特定條件,跟随着cse跳轉的會是整個的blocks 

l -frerun-cse-after-loop:在循環優化完成後,重新進行公用子表達式消元操作。 

l -frerun-loop-opt:兩次運作循環優化 l -fgcse:執行全局公用子表達式消除pass。這個pass還執行全局常量和copy propagation。這些優化操作試圖分析生成的彙編語言代碼并且結合通用片段, 消除備援的代碼段。如果代碼使用計算性的goto, gcc指令推薦使用-fno-gcse選項。 

l-fgcse-lm:全局公用子表達式消除将試圖移動那些僅僅被自身存儲kill的裝載操作的位置。這将允許将循環内的load/store操作序列中的load轉移到循環的外面(隻需要裝載一次),而在循環内改變成copy/store序列。在選中-fgcse後,預設打開。 

l -fgcse-sm:當一個存儲操作pass在一個全局公用子表達式消除的後面,這個pass将試圖将store操作轉移到循環外面去。如果與-fgcse-lm配合使用,那麼load/store操作将會轉變為在循環前load,在循環後store,進而提高運作效率,減少不必要的操作。 

l -fgcse-las:全局公用子表達式消除pass将消除在store後面的不必要的load操作,這些load與store通常是同一塊存儲單元(全部或局部) 

l-fdelete-null-pointer-checks:通過對全局資料流的分析,識别并排出無用的對空指針的檢查。編譯器假設間接引用空指針将停止程式。 如果在間接引用之後檢查指針,它就不可能為空。 

l -fexpensive-optimizations:進行一些從編譯的角度來說代價高昂的優化(這種優化據說對于程式執行未必有很大的好處,甚至有可能降低執行效率,具體不是很清楚) 

l -fregmove:編譯器試圖重新配置設定move指令或者其他類似操作數等簡單指令的寄存器數目,以便最大化的捆綁寄存器的數目。這種優化尤其對雙操作數指令的機器幫助較大。 

l -fschedule-insns:編譯器嘗試重新排列指令,用以消除由于等待未準備好的資料而産生的延遲。這種優化将對慢浮點運算的機器以及需要load memory的指令的執行有所幫助,因為此時允許其他指令執行,直到load memory的指令完成,或浮點運算的指令再次需要cpu。 l 

-fschedule-insns2:與-fschedule-insns相似。但是當寄存器配置設定完成後,會請求一個附加的指令計劃pass。這種優化對寄存器較小,并且load memory操作時間大于一個時鐘周期的機器有非常好的效果。 

l -fsched-interblock:這種技術使編譯器能夠跨越指令塊排程指令。 這可以非常靈活地移動指令以便等待期間完成的工作最大化。 

l -fsched-spec-load:允許一些load指令進行一些投機性的動作。(具體不詳)相同功能的還有-fsched-spec-load-dangerous,允許更多的load指令進行投機性操作。這兩個選項在選中-fschedule-insns時預設打開。 

l -fcaller-saves:通過存儲和恢複call調用周圍寄存器的方式,使被call調用的value可以被配置設定給寄存器,這種隻會在看上去能産生更好的代碼的時候才被使用。(如果調用多個函數, 這樣能夠節省時間, 因為隻進行一次寄存器的儲存和恢複操作, 而不是在每個函數調用中都進行。) 

l -fpeephole2:允許計算機進行特定的觀察孔優化(這個不曉得是什麼意思),-fpeephole與-fpeephole2的差别在于不同的編譯器采用不同的方式,由的采用-fpeephole,有的采用-fpeephole2,也有兩種都采用的。 

l -freorder-blocks:在編譯函數的時候重新安排基本的塊,目的在于減少分支的個數,提高代碼的局部性。 

l -freorder-functions:在編譯函數的時候重新安排基本的塊,目的在于減少分支的個數,提高代碼的局部性。這種優化的實施依賴特定的已存在的資訊:.text.hot用于告知通路頻率較高的函數,.text.unlikely用于告知基本不被執行的函數。 

l -fstrict-aliasing:這種技術強制實行進階語言的嚴格變量規則。 對于c和c++程式來說, 它確定不在資料類型之間共享變量. 例如, 整數變量不和單精度浮點變量使用相同的記憶體位置。 

l -funit-at-a-time:在代碼生成前,先分析整個的彙編語言代碼。這将使一些額外的優化得以執行,但是在編譯器間需要消耗大量的記憶體。(有資料介紹說:這使編譯器可以重新安排不消耗大量時間的代碼以便優化指令緩存。) 

l -falign-functions:這個選項用于使函數對準記憶體中特定邊界的開始位置。 大多數處理器按照頁面讀取記憶體,并且確定全部函數代碼位于單一記憶體頁面内, 就不需要叫化代碼所需的頁面。 

l -falign-jumps:對齊分支代碼到2的n次方邊界。在這種情況下,無需執行傀儡指令(dummy operations) 

l -falign-loops:對齊循環到2的n次幂邊界。期望可以對循環執行多次,用以補償運作dummy operations所花費的時間。 

l -falign-labels:對齊分支到2的n次幂邊界。這種選項容易使代碼速度變慢,原因是需要插入一些dummy operations當分支抵達usual flow of the code. 

l -fcrossjumping:這是對跨越跳轉的轉換代碼處理, 以便組合分散在程式各處的相同代碼。 這樣可以減少代碼的長度, 但是也許不會對程式性能有直接影響。  

-O3: 比O2更進一步的進行優化。

在包含了O2所有的優化的基礎上,又打開了以下優化選項: 

l -finline-functions:内聯簡單的函數到被調用函數中。由編譯器啟發式的決定哪些函數足夠簡單可以做這種内聯優化。預設情況下,編譯器限制内聯的尺寸,3.4.6中限制為600(具體含義不詳,指令條數或代碼size?)可以通過-finline-limit=n改變這個長度。這種優化技術不為函數建立單獨的彙編語言代碼, 而是把函數代碼包含在排程程式的代碼中。 對于多次被調用的函數來說, 為每次函數調用複制函數代碼。 雖然這樣對于減少代碼長度不利, 但是通過最充分的利用指令緩存代碼, 而不是在每次函數調用時進行分支操作, 可以提高性能。 

l -fweb:建構用于儲存變量的僞寄存器網絡。 僞寄存器包含資料, 就像他們是寄存器一樣, 但是可以使用各種其他優化技術進行優化, 比如cse和loop優化技術。這種優化會使得調試變得更加的不可能,因為變量不再存放于原本的寄存器中。 

l -frename-registers:在寄存器配置設定後,通過使用registers left over來避免預定代碼中的虛假依賴。這會使調試變得非常困難,因為變量不再存放于原本的寄存器中了。 

l -funswitch-loops:将無變化的條件分支移出循環,取而代之的将結果副本放入循環中。 

 -Os: 主要是對程式的尺寸進行優化。打開了大部分O2優化中不會增加程式大小的優化選項,并對程式代碼的大小做更深層的優化。(通常我們不需要這種優化)Os會關閉如下選項: -falign-functions -falign-jumps -falign-loops  -falign-labels   -freorder-blocks   -fprefetch-loop-arrays  

優化介紹小結 O0選項不進行任何優化,在這種情況下,編譯器盡量的縮短編譯消耗(時間,空間),此時,debug會産出和程式預期的結果。當程式運作被斷點打斷,此時程式内的各種聲明是獨立的,我們可以任意的給變量指派,或者在函數體内把程式計數器指到其他語句,以及從源程式中 精确地擷取你期待的結果. 

O1優化會消耗少多的編譯時間,它主要對代碼的分支,常量以及表達式等進行優化。 

O2會嘗試更多的寄存器級的優化以及指令級的優化,它會在編譯期間占用更多的記憶體和編譯時間。 

O3在O2的基礎上進行更多的優化,例如使用僞寄存器網絡,普通函數的内聯,以及針對循環的更多優化。 

Os主要是對代碼大小的優化,我們基本不用做更多的關心。 通常各種優化都會打亂程式的結構,讓調試工作變得無從着手。并且會打亂執行順序,依賴記憶體操作順序的程式需要做相關處理才能確定程式的正确性。  

優化代碼有可能帶來的問題 

1.調試問題:正如上面所提到的,任何級别的優化都将帶來代碼結構的改變。例如:對分支的合并和消除,對公用子表達式的消除,對循環内load/store操作的替換和更改等,都将會使目标代碼的執行順序變得面目全非,導緻調試資訊嚴重不足。 

2.記憶體操作順序改變所帶來的問題:在O2優化後,編譯器會對影響記憶體操作的執行順序。例如:-fschedule-insns允許資料處理時先完成其他的指令;-fforce-mem有可能導緻記憶體與寄存器之間的資料産生類似髒資料的不一緻等。對于某些依賴記憶體操作順序而進行的邏輯,需要做嚴格的處理後才能進行優化。例如,采用volatile關鍵字限制變量的操作方式,或者利用barrier迫使cpu嚴格按照指令序執行的。

————————————————

繼續閱讀