歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網

gcc arm

日期:2017/3/3 12:33:31   编辑:Linux技術

Gcc優化調研

Gcc編譯優化簡介

gcc提供了為了滿足用戶不同程度的的優化需要,提供了近百種優化選項,用來對{編譯時間,目標文件長度,執行效率}這個三維模型進行不同的取捨和平衡。優化的方法不一而足,總體上將有以下幾類:1)精簡操作指令;2)盡量滿足cpu的流水操作;3)通過對程序行為地猜測,重新調整代碼的執行順序;4)充分使用寄存器;5)對簡單的調用進行展開等等。想全部了解這些編譯選項,並在其中挑選適合的選項進行優化,無疑像個噩夢般的過程。單從gnu的官方網站上得到的手冊來看,描述依然比較蒼白,不足以完全了解選項的使用范圍和原理。(GCChaswelloverahundredindividualoptimizationflagsanditwouldbeinsanetotryanddescribethemall)

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

-O0:

不做任何優化,這是默認的編譯選項。
-O和-O1:

對程序做部分編譯優化,對於大函數,優化編譯占用稍微多的時間和相當大的內存。使用本項優化,編譯器會嘗試減小生成代碼的尺寸,以及縮短執行時間,但並不執行需要占用大量編譯時間的優化。

打開的優化選項:

l-fdefer-pop:延遲棧的彈出時間。當完成一個函數調用,參數並不馬上從棧中彈出,而是在多個函數被調用後,一次性彈出。
l-fmerge-constants:嘗試橫跨編譯單元合並同樣的常量(stringconstantsandfloatingpointconstants)

[code]l
-fthread-jumps:
如果某個跳轉分支的目的地存在另一個條件比較,而且該條件比較包含在前一個比較語句之內,那麼執行本項優化.根據條件是true或者false,前面那條分支重定向到第二條分支的目的地或者緊跟在第二條分支後面.
l-floop-optimize:執行循環優化,將常量表達式從循環中移除,簡化判斷循環的條件,並且optionallydostrength-reduction,或者將循環打開等。在大型復雜的循環中,這種優化比較顯著。

l-fif-conversion:嘗試將條件跳轉轉換為等價的無分支型式。優化實現方式包括條件移動,min,max,設置標志,以及abs指令,以及一些算術技巧等。
l-fif-conversion2基本意義相同,沒有找到更多的解釋。

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

l-fguess-branch-probability:當沒有可用的profilingfeedback或__builtin_expect時,編譯器采用隨機模式猜測分支被執行的可能性,並移動對應匯編代碼的位置,這有可能導致不同的編譯器會編譯出迥然不同的目標代碼。
l-fcprop-registers:因為在函數中把寄存器分配給變量,所以編譯器執行第二次檢查以便減少調度依賴性(兩個段要求使用相同的寄存器)並且刪除不必要的寄存器復制操作。

-O2:

是比O1更高級的選項,進行更多的優化。Gcc將執行幾乎所有的不包含時間和空間折中的優化。當設置O2選項時,編譯器並不進行循環打開()
loopunrolling
以及函數內聯。與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還執行全局常量和copypropagation。這些優化操作試圖分析生成的匯編語言代碼並且結合通用片段,消除冗余的代碼段。如果代碼使用計算性的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:編譯器嘗試重新排列指令,用以消除由於等待未准備好的數據而產生的延遲。這種優化將對慢浮點運算的機器以及需要loadmemory的指令的執行有所幫助,因為此時允許其他指令執行,直到loadmemory的指令完成,或浮點運算的指令再次需要cpu。

l-fschedule-insns2:與-fschedule-insns相似。但是當寄存器分配完成後,會請求一個附加的指令計劃pass。這種優化對寄存器較小,並且loadmemory操作時間大於一個時鐘周期的機器有非常好的效果。
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次方邊界。在這種情況下,無需執行傀儡指令(dummyoperations)

l-falign-loops:對齊循環到2的n次冪邊界。期望可以對循環執行多次,用以補償運行dummyoperations所花費的時間。
l-falign-labels:對齊分支到2的n次冪邊界。這種選項容易使代碼速度變慢,原因是需要插入一些dummyoperations當分支抵達usualflowofthecode.

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

比O2更進一步的進行優化。在包含了O2所有的優化的基礎上,又打開了以下優化選項:
l
-finline-functions
:內聯簡單的函數到被調用函數中。由編譯器啟發式的決定哪些函數足夠簡單可以做這種內聯優化。默認情況下,編譯器限制內聯的尺寸,3.4.6中限制為600(具體含義不詳,指令條數或代碼size?)可以通過-finline-limit=n改變這個長度。這種優化技術不為函數創建單獨的匯編語言代碼,而是把函數代碼包含在調度程序的代碼中。對於多次被調用的函數來說,為每次函數調用復制函數代碼。雖然這樣對於減少代碼長度不利,但是通過最充分的利用指令緩存代碼,而不是在每次函數調用時進行分支操作,可以提高性能。

l
-fweb
:構建用於保存變量的偽寄存器網絡。偽寄存器包含數據,就像他們是寄存器一樣,但是可以使用各種其他優化技術進行優化,比如cse和loop優化技術。這種優化會使得調試變得更加的不可能,因為變量不再存放於原本的寄存器中。
l
-frename-registers
:在寄存器分配後,通過使用registersleftover來避免預定代碼中的虛假依賴。這會使調試變得非常困難,因為變量不再存放於原本的寄存器中了。

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嚴格按照指令序執行的。

3.效率是否有較大的提高有待試驗論證:並不是所有的優化都會對執行效率產生積極的作用,有的時候利用優化會起到適得其反的效果。這就需要在試驗的基礎上來不斷調整優化選項,來謀求最佳優化效果。但通常這樣做所投入的時間和最終產生的效果之間比較,往往得不償失。

代碼質量

從代碼質量來看,大部分的公用庫的代碼可以承受O2級別的優化(大部分開源的服務器程序都使用O2優化)。但對具有全局數據的模塊,以及一些依賴內存執行順序的模塊,需要特別的注意並進行一定范圍的修改。例如:

Pendingpool中有類似如下的代碼:

if(m_aySocket[index].nSock>0){

close(m_aySocket[index].nSock);

m_aySocket[index].nSock=-1;

}

m_aySocket[index].status=NOT_USED;

在這種應用中,對status的改變需要滯後於對nSock的改變,否則在多線程模式下,將會出現數據混亂,甚至會出core。此時需要對此段代碼進行特殊處理(使用barrier等),以確保內存操作順序。

又例如:

在comdb的應用中,認為對u_int類型的賦值操作是原子的,基於這種原因對索引操作未加任何的鎖控制。如果進行編譯優化,這種假設就不再成立了,需要對相關的變量加volatile關鍵字限制才行。

總結:

1.采用gcc的優化會帶來諸多問題,但的確可以提高cpu密集型的代碼的執行效率。2.目前公共庫尚不具備全面進行優化的可能性,主要是優化代價高昂。

3.可以選擇具有代表性的庫進行優化試驗,積累一些經驗(與stl配合完成)。

Copyright © Linux教程網 All Rights Reserved