Optimizing Code with GCC
現(xiàn)在的編譯器越來(lái)越聰明,功能越來(lái)越強(qiáng),從簡(jiǎn)單的函數(shù)內(nèi)聯(lián),到復(fù)雜的寄存器分析,一系列代碼革命使程序運(yùn)行得越來(lái)越快。大多數(shù)時(shí)候,更快比更小重要,因?yàn)榇疟P空間和內(nèi)存都變得便宜了。但是在嵌入式系統(tǒng)里,更小和更快是一樣重要的,所以把代碼進(jìn)行優(yōu)化是非常有意義的工作。
如果你已經(jīng)知道了怎樣用gcc編譯你的代碼,現(xiàn)在是時(shí)候讓你的代碼更快或者更小了,這也是本章的內(nèi)容。如果學(xué)有所成的話,你甚至可以讓你的下一個(gè)程序既快又小。首先我們快速瀏覽一下編譯器優(yōu)化理論,然后討論GCC的代碼優(yōu)化命令行選項(xiàng),從一般的、體系結(jié)構(gòu)無(wú)關(guān)的優(yōu)化,到體系結(jié)構(gòu)相關(guān)的優(yōu)化方法。
雖然本章的示例代碼都是C語(yǔ)言的,但是優(yōu)化選項(xiàng)是通用的、語(yǔ)言無(wú)關(guān)的。能把一些優(yōu)化選項(xiàng)適用到所有語(yǔ)言的編譯器上,是一個(gè)編譯器家族最大的優(yōu)勢(shì),比如GCC編譯器家族就是這樣。
OPTIMIZATION AND DEBUGGING
沒(méi)有代碼優(yōu)化的時(shí)候,GCC的一個(gè)重要目標(biāo)是盡量縮短編譯時(shí)間,并保證產(chǎn)生的代碼在調(diào)試環(huán)境下的行為正確。比如,在優(yōu)化過(guò)的代碼里,一個(gè)變量如果在循環(huán)里多次計(jì)算,但是值其實(shí)沒(méi)有變化,那么編譯器可以把它移到循環(huán)的外面,只計(jì)算一次。雖然這是可行了(當(dāng)然,只要不改變程序的運(yùn)行結(jié)果),但是這使你無(wú)法按照源代碼進(jìn)行調(diào)試,因?yàn)橛?jì)算該變量的代碼被優(yōu)化掉了。如果沒(méi)有優(yōu)化,你就可以正確的進(jìn)行調(diào)試,檢查變量的值。這就是所謂的“代碼在調(diào)試環(huán)境下的行為正確”。
優(yōu)化能改變代碼的執(zhí)行流程,但不改變執(zhí)行結(jié)果。所以,優(yōu)化一般是編碼并調(diào)試完成之后才進(jìn)行的。其實(shí)優(yōu)化過(guò)的代碼也是可以進(jìn)行調(diào)試的,只是需要一些技巧。
編譯器優(yōu)化理論概覽
代碼優(yōu)化是指分析一段編譯后的代碼,然后決定如何改變它,使它運(yùn)行的更快,消耗的資源更少。擁有此功能的編譯器叫做優(yōu)化編譯器(optimizing compilers),最后的輸出代碼叫做優(yōu)化代碼(optimized code)。
優(yōu)化編譯器使用幾種辦法來(lái)決定哪些代碼可被優(yōu)化。一種是控制流分析(control flow analysis),即檢查循環(huán)和其他控制語(yǔ)句,比如if-then和case,找出程序可能的執(zhí)行路徑,然后決定哪些執(zhí)行路徑可以被改進(jìn)。另一個(gè)典型的優(yōu)化技術(shù)是檢查數(shù)據(jù)是怎樣使用的,即數(shù)據(jù)流分析(data flow analysis)。此法檢查變量在哪里是怎樣使用的(或沒(méi)被使用),然后應(yīng)用一系列的方程式到這些使用模式上面,從而找到優(yōu)化的途徑。
除了本章所述的一些計(jì)算機(jī)所作的改進(jìn)外,優(yōu)化還包括了對(duì)程序所使用的算法的改進(jìn)。典型的比如冒泡排序算法改進(jìn)成快速排序或希爾排序。這類改進(jìn)能使程序的性能有本質(zhì)的提高,比計(jì)算機(jī)能做的優(yōu)化強(qiáng)得多,所以優(yōu)化既是CPU的事情,也是人的事情。
本節(jié)定義一個(gè)基本塊(basic block)指,只有一個(gè)入口和出口,其他地方不包括終止、分支語(yǔ)句的連續(xù)代碼段。在一個(gè)基本塊內(nèi)進(jìn)行的轉(zhuǎn)化稱為局部轉(zhuǎn)化(local transformations),同樣的不是在一個(gè)基本塊內(nèi)的轉(zhuǎn)化稱為全局轉(zhuǎn)化(global transformations)。通常編譯器會(huì)進(jìn)行許多全局或局部的轉(zhuǎn)化,不過(guò)局部轉(zhuǎn)化總是先做。
雖然本節(jié)的例子使用C語(yǔ)言,其實(shí)所有GCC編譯器都使用一種中間語(yǔ)言進(jìn)行這種轉(zhuǎn)化,這種中間語(yǔ)言比各種編程語(yǔ)言更適合計(jì)算機(jī)處理。GCC在產(chǎn)生最終的2進(jìn)制代碼前,會(huì)使用一系列不同的中間語(yǔ)言翻譯你的源程序。不過(guò)對(duì)人類來(lái)說(shuō),C和其他的高級(jí)語(yǔ)言比這些中間語(yǔ)言更好理解。
GCC編譯器還做了很多其他的優(yōu)化,有些非常細(xì)致,甚至需要專業(yè)的編譯器理論知識(shí)。這里列出的優(yōu)化方法只是基礎(chǔ),并且能用命令行來(lái)進(jìn)行選擇。
注意
我所知的最經(jīng)典的編譯器著作,當(dāng)屬“龍書”《Compilers: Principles, Techniques, and Tools》,因其封面有一個(gè)恐龍而得名(Alfred V. Aho, Ravi Sethi, and Jeffrey D. Ullman,Addison Wesley Longman, 1986. ISBN: 0-201-10088-6)。書里的優(yōu)化理論介紹比本文詳細(xì)的多,并且曾是我的啟蒙書籍。
Code Motion
Code motion是一種優(yōu)化技巧,是指在Common subexpression elimination(后有詳述)時(shí)去掉多余的代碼。Code motion并不是去掉所有的subexpression,而是在中間語(yǔ)言形式下改變它們的位置,以便能減少它們出現(xiàn)的次數(shù)。比如,在嵌套的循環(huán)或其他控制結(jié)構(gòu)里,中間變量的計(jì)算次數(shù)可能不是最優(yōu)的,要優(yōu)化這些程序,編譯器把這些計(jì)算語(yǔ)句移到循環(huán)更少的地方,并且保證計(jì)算結(jié)果是一樣的。把計(jì)算移出循環(huán)的方法我們稱為loop-invariant code motion。Code motion還用在另一種Common subexpression elimination里,叫做partial redundancy elimination。
Common Subexpression Elimination
去除多余的計(jì)算是一種標(biāo)準(zhǔn)的優(yōu)化手段,因?yàn)樗軠p少程序的指令數(shù),并得到相同的結(jié)果。比如,如果一個(gè)表達(dá)式的參數(shù)(所引用的變量)值不變,那么就可以只計(jì)算一次結(jié)果,在以后引用該表達(dá)式的地方用結(jié)果值替代就可以了。這些后來(lái)引用該表達(dá)式的地方就叫做common subexpressions。比如下例:
Listing 5-1. An Example of a Common Subexpression
#define STEP 3
#define SIZE 100
int i, j, k;
int p[SIZE];
for (i = 0; i < SIZE; ++i) {
??? j = 2 * STEP;
??? k = p[i] * j;
}
for循環(huán)內(nèi)的表達(dá)式j = 2 * STEP就是一個(gè)common subexpression,因?yàn)樗闹悼梢栽谶M(jìn)入循環(huán)之前計(jì)算,而且它的參數(shù)變量STEP(實(shí)際上是一個(gè)宏定義)從不改變。Common subexpression elimination (CSE)把for循環(huán)內(nèi)的重復(fù)計(jì)算去掉,成為如下形式:
j = 2 * STEP;
for (i = 0; i < 100; ++i) {
??? k = p[i] * j;
}
雖然這個(gè)例子簡(jiǎn)單了點(diǎn),不過(guò)能很好的說(shuō)明問(wèn)題,CSE去掉了100次對(duì)j的計(jì)算。CSE能去掉不必要的計(jì)算,改善程序性能,并減少了最終文件的大小。
Constant Folding
Constant folding是指去掉在編譯時(shí)就能確定的數(shù)值計(jì)算表達(dá)式。這些表達(dá)式必須只包含常數(shù)值,或者值為常數(shù)的變量。比如,下面的計(jì)算表達(dá)式都可以用一個(gè)賦值語(yǔ)句來(lái)替換:
n = 10 * 20 * 400;
?
i = 10;
j = 20;
ij = i * j;
后面的例子里,如果i和j在后續(xù)的程序里沒(méi)有用到的話,完全可以去除它們的定義。
Copy Propagation Transformations
這是另一種減少或去除多余計(jì)算的方法,即去除那些只是為了傳遞數(shù)值的變量復(fù)制操作。看下面的代碼:
i = 10;
x[j] = i;
y[MIN] = b;
b = i;
Copy propagation transformation可能會(huì)優(yōu)化成下面的代碼:
i = 10;
x[j] = 10;
y[MIN] = b;
b = 10;
在本例里,copy propagation允許把右值變成常數(shù),這樣比搜索和復(fù)制變量的值要快得多,并且也能去掉變量給自己賦值的情況。有些情況下,copy propagation并不直接產(chǎn)生優(yōu)化,但是能簡(jiǎn)化代碼,方便其他優(yōu)化,比如code motion和code elimination。
Constant propagation是一種把變量替換成常量的copy propagation transformation優(yōu)化方法。Copy propagation主要指去掉不必要的變量間的相互復(fù)制,而Constant propagation則指去掉不必要的預(yù)定義值到變量的復(fù)制。
Dead Code Elimination
DCE是指把那些實(shí)際上無(wú)用的或多余的代碼去掉。你可能想問(wèn)“為什么我會(huì)寫這樣的代碼呢?”,其實(shí)在一個(gè)很大的、延續(xù)時(shí)間很長(zhǎng)的項(xiàng)目里,這是很容易發(fā)生的。許多DCE都是在中間語(yǔ)言表示的形式下進(jìn)行的,因?yàn)樗窃创a更標(biāo)準(zhǔn)的翻譯,更容易發(fā)現(xiàn)不必要的中間計(jì)算。
Unreachable code elimination是指去除編譯器確定不可能到達(dá)的代碼。比如下面的代碼塊:
if ( i == 10 ) {
??? . . .
} else {
??? . . .
??? if ( i == 10) {
??????? . . .
??? }
}
第2次對(duì)i是否等于10的測(cè)試及處理代碼可以去掉,因?yàn)樗遣豢赡艿竭_(dá)的。UCE也是在中間代碼形式里進(jìn)行的。
If-Conversion
即分支重構(gòu),比如把大的if-then-elseif-else結(jié)構(gòu),重構(gòu)成多個(gè)if語(yǔ)句,這樣能簡(jiǎn)化代碼,為以后的優(yōu)化提供方便,并去除某些無(wú)用的跳轉(zhuǎn)和分支語(yǔ)句。
Inlining
即把復(fù)雜結(jié)構(gòu)或函數(shù)調(diào)用替換成內(nèi)聯(lián)的代碼以改善性能。Code inlining或loop unrolling都是指把全部或部分循環(huán)展開(kāi)成一系列的直接指令。Function inlining是指用函數(shù)執(zhí)行的指令替代對(duì)函數(shù)的調(diào)用。一般情況下,inlining能減少代碼復(fù)雜度,提高性能,因?yàn)椴恍枰嘤嗟姆种D(zhuǎn)。它還能給common subexpression elimination和code motion提供優(yōu)化機(jī)會(huì)。這方面最經(jīng)典的例子是Duff’s Device,詳見(jiàn)http://en.wikipedia.org/wiki/Duff's_device。
GCC Optimization Basics
GCC處理源代碼時(shí),會(huì)把它轉(zhuǎn)化成一種中間形式。這樣做有幾大好處:、
l? 把源代碼變得簡(jiǎn)單、低級(jí),使優(yōu)化點(diǎn)暴露出來(lái);
l? 使可能很復(fù)雜的結(jié)構(gòu)更容易生成簡(jiǎn)單易讀的語(yǔ)法分析樹(shù);
l? 使用統(tǒng)一的中間形式使GCC編譯器之間能通用優(yōu)化策略。
傳統(tǒng)上GCC使用的內(nèi)部中間形式叫做Register Transfer Language (RTL),這是一種很低級(jí)的語(yǔ)言,GCC把任何代碼(無(wú)論什么級(jí)別)轉(zhuǎn)化成目標(biāo)代碼之前都先翻譯成這種代碼。對(duì)于像GCC的RTL這種非常低級(jí)的語(yǔ)言進(jìn)行的優(yōu)化也是很“低級(jí)”的,比如寄存器分配、堆棧和數(shù)據(jù)優(yōu)化等。因?yàn)樗艿图?jí),所以不會(huì)像你想象的那樣能進(jìn)行數(shù)據(jù)類型、數(shù)組和變量引用、控制流改變等“高級(jí)”的優(yōu)化。
GCC 4.0的作者們發(fā)明了一種新的中間形式static single assignment (SSA),通過(guò)對(duì)GCC編譯器產(chǎn)生的語(yǔ)法分析樹(shù)進(jìn)行操作而得到,因此得名Tree SSA。4.0及更高的GCC編譯器在生成Tree SSA之前還有2種中間形式,叫做GENERIC和GIMPLE。GENERIC是通過(guò)去除源代碼中語(yǔ)言相關(guān)的結(jié)構(gòu)得到的中間形式,GIMPLE則是把GENERIC只讀地址引用進(jìn)行簡(jiǎn)化得到。也許你也看出來(lái)了,在到達(dá)RTL等級(jí)之前,有許多的優(yōu)化已經(jīng)在這些相對(duì)高級(jí)點(diǎn)的層面上先做了。
關(guān)于Tree SSA的詳細(xì)信息和優(yōu)化處理的步驟有許多資料可參考,其中一個(gè)是2003年的GCC開(kāi)發(fā)者總結(jié),網(wǎng)址為http://www.linux.org.uk/~ajh/gcc/gccsummit-2003-proceedings.pdf。
What’s New in GCC 4.x Optimization
GCC 4.x家族最重要的變化是引入了中間形式Tree SSA,它提供了更多的優(yōu)化空間,和更多的參數(shù)選項(xiàng),包括-ftree-ccp, -ftree-ch, -ftree-copyrename, -ftree-dce, -ftree-dominator-opts, -ftree-dse, -ftree-fre, -ftree-loop-im, -ftree-loop-ivcanon, -ftree-loop-linear, -ftree-loop-optimize, -ftree-lrs, -ftree-pre, -ftree-sra, -ftree-ter, and -ftree-vectorize,本章稍候會(huì)敘述。由于有了這些重大的改變,原來(lái)的通用優(yōu)化等級(jí)-O1、-O2、-O3和-Os都有了變化。除此以外,任何語(yǔ)言的GCC編譯器的優(yōu)化都更普遍了。
同時(shí)由于有IBM的大力支持,GCC 4改進(jìn)了向量化。向量化發(fā)現(xiàn)同一操作應(yīng)用到多個(gè)數(shù)據(jù)的代碼,并改善其性能。GCC 4可以把16個(gè)標(biāo)量操作合成為一個(gè)向量操作。這個(gè)優(yōu)化方法可以在游戲、視頻和多媒體應(yīng)用里大展身手,因?yàn)檫@些程序的指令都是對(duì)數(shù)組向量的重復(fù)操作。
GCC 4還改進(jìn)了數(shù)組邊界檢查和棧的內(nèi)容結(jié)構(gòu)檢查,保護(hù)程序免遭流行的緩沖區(qū)和棧溢出攻擊。
Architecture-Independent Optimizations
GCC的優(yōu)化分為2大類:體系結(jié)構(gòu)無(wú)關(guān)和體系結(jié)構(gòu)相關(guān)。本節(jié)介紹體系結(jié)構(gòu)無(wú)關(guān)的優(yōu)化,包括計(jì)算機(jī)體系無(wú)關(guān),比如x86;處理器類型無(wú)關(guān),比如IA-32處理器;和處理器家族無(wú)關(guān),例如Pentium IV (Xeon)。
GCC的優(yōu)化選項(xiàng)有-O;-On,參數(shù)n是介于0到3之間的整數(shù);或者-Os。-O0關(guān)閉優(yōu)化。-O和-O1(又叫作第1級(jí)優(yōu)化)等價(jià),允許編譯器在不大量增加編譯時(shí)間的前提下減少代碼量和執(zhí)行時(shí)間。-O2和-O3比-O1的優(yōu)化等級(jí)更高,-Os會(huì)最小化代碼量。
本節(jié)所有的表格顯示了GCC提供的各種優(yōu)化選項(xiàng),如果要關(guān)閉相應(yīng)優(yōu)化,只需在-f和優(yōu)化選項(xiàng)名字之間加上no-就行了。比如,要禁止deferred stack pops優(yōu)化,命令行可以這樣寫:
$ gcc myprog.c -o myprog -O1 -fno-defer-pop
注意
-f表示一個(gè)機(jī)器無(wú)關(guān)的操作標(biāo)志,即應(yīng)用一個(gè)(大多數(shù)情況下)體系結(jié)構(gòu)無(wú)關(guān)的優(yōu)化操作。這些標(biāo)志選項(xiàng)更改了GCC的默認(rèn)行為,但是不需要硬件的特殊支持。通常你可以指定多個(gè)標(biāo)志。
Level 1 GCC Optimizations
下表列出了-O或-O1時(shí)進(jìn)行的默認(rèn)優(yōu)化選項(xiàng):
?
| Optimization | Description |
| -fcprop-registers | 試圖減少寄存器復(fù)制操作的次數(shù) |
| -fdefer-pop | Accumulates function arguments on the stack. |
| -fdelayed-branch | Utilizes instruction slots available after delayed branch instructions. |
| -fguess-branch-probability | 利用隨機(jī)預(yù)測(cè)器猜測(cè)分支的可達(dá)性 |
| -fif-conversion | 把有條件跳轉(zhuǎn)變成非分支語(yǔ)句 |
| -fif-conversion2 | 利用條件執(zhí)行(要求CPU支持)進(jìn)行if-conversion優(yōu)化 |
| -floop-optimize | 應(yīng)用幾個(gè)針對(duì)循環(huán)的優(yōu)化 |
| -fmerge-constants | 合并多個(gè)模塊中相等的常量 |
| -fomit-frame-pointer | 省略函數(shù)楨指針的存儲(chǔ)。只能在不影響調(diào)試的系統(tǒng)里激活 |
| -ftree-ccp | 在SSA Trees上進(jìn)行較少的conditional constant propagation(CCP)優(yōu)化(只限GCC 4.x) |
| -ftree-ch | 在SSA Trees上執(zhí)行loop header copying,即去掉一個(gè)跳轉(zhuǎn)指令,并提供code motion優(yōu)化的機(jī)會(huì)(只限GCC 4.x) |
| -ftree-copyrename | 在SSA Trees上執(zhí)行copy renaming,即在復(fù)制位置把內(nèi)部變量的名字改得更接近原始變量的名字(只限GCC 4.x) |
| -ftree-dce | 在SSA Trees上執(zhí)行dead code elimination (DCE)優(yōu)化(只限GCC 4.x) |
| -ftree-dominator-opts | 利用支配樹(shù)(dominator tree)遍歷來(lái)進(jìn)行一系列優(yōu)化。A dominator tree is a tree where each node’s children are the nodes that it immediately dominates。這些優(yōu)化包括constant/copy propagation,redundancy elimination,range propagation,expression simplification和jump threading(減少跳轉(zhuǎn)語(yǔ)句)(只限GCC 4.x) |
| -ftree-dse | 在SSA Trees上執(zhí)行dead store elimination (DSE)(只限GCC 4.x) |
| -ftree-fre | 在SSA Trees上執(zhí)行full redundancy elimination (FRE),即認(rèn)為全路徑計(jì)算的表達(dá)式會(huì)導(dǎo)致冗余編譯。這和partial redundancy elimination(PRE)相似,不過(guò)比它快,找到的冗余也比較少。(只限GCC 4.x) |
| -ftree-lrs | 在SSA Trees轉(zhuǎn)化成RTL前,轉(zhuǎn)化成一般形式,并執(zhí)行live range splitting。這種方法明確了變量的生存期,為后續(xù)的優(yōu)化提供幫助(只限GCC 4.x) |
| -ftree-sra | 把聚合體替換成標(biāo)量,即把對(duì)結(jié)構(gòu)體的引用替換成標(biāo)量數(shù)值,避免在不必要的時(shí)候把結(jié)構(gòu)體提交到內(nèi)存里(只限GCC 4.x) |
| -ftree-ter | 在SSA Trees轉(zhuǎn)化成RTL前,轉(zhuǎn)化成一般形式,并執(zhí)行temporary expression replacement (TER)。把只使用一次的臨時(shí)表達(dá)式替換成原始定義的表達(dá)式,這樣更容易產(chǎn)生RTL代碼,并使產(chǎn)生的RTL代碼有更多的優(yōu)化機(jī)會(huì)。(只限GCC 4.x) |
第1級(jí)優(yōu)化揉合了代碼大小和速度改進(jìn)2種優(yōu)化措施。比如,-tree-dce去掉了無(wú)用代碼,于是減少了代碼量;跳轉(zhuǎn)指令減少使整個(gè)程序的棧使用量減少;而-fcprop-registers是性能優(yōu)化,減少在寄存器間復(fù)制數(shù)據(jù)的次數(shù)。
-fdelayed-branch和-fguess-branch-probability是指令調(diào)度改進(jìn)。如果底層CPU支持指令調(diào)度,這些優(yōu)化標(biāo)志就試圖使CPU等待下一條指令的等待時(shí)間最小化。
-floop-optimize開(kāi)啟了對(duì)循環(huán)的優(yōu)化,包括把常數(shù)表達(dá)式移出循環(huán)和簡(jiǎn)化推出循環(huán)的條件測(cè)試。在更高的第2級(jí)優(yōu)化里,該標(biāo)志還執(zhí)行strength reduction和循環(huán)展開(kāi)。
-fomit-frame-pointer是非常有用,原因有2個(gè):省下了設(shè)置、保存和恢復(fù)楨指針的代碼;有時(shí)候省下了一個(gè)CPU寄存器,可有它用。而負(fù)面影響是:沒(méi)有了楨指針,調(diào)試(比如棧跟蹤,尤其是嵌套很深的函數(shù))變得很難甚至不可能。
-O2優(yōu)化(第2級(jí)優(yōu)化)包括了第1級(jí)的所有優(yōu)化加上下表列出的另一些優(yōu)化。應(yīng)用這些優(yōu)化將延長(zhǎng)編譯時(shí)間,不過(guò)你的程序性能將得到顯著的提高。
Level 2 GCC Optimizations
當(dāng)使用-O2優(yōu)化選項(xiàng)時(shí),下表的優(yōu)化將默認(rèn)進(jìn)行:
| Optimization | Description |
| -falign-functions | 把函數(shù)對(duì)齊到2的指數(shù)字節(jié)邊界 |
| -falign-jumps | 把跳轉(zhuǎn)指令對(duì)齊到2的指數(shù)字節(jié)邊界 |
| -falign-labels | 把標(biāo)簽對(duì)齊到2的指數(shù)字節(jié)邊界 |
| -falign-loops | 把循環(huán)對(duì)齊到2的指數(shù)字節(jié)邊界 |
| -fcaller-saves | 保存并恢復(fù)被函數(shù)調(diào)用改寫的寄存器 |
| -fcrossjumping | 分解等價(jià)代碼來(lái)減少代碼量 |
| -fcse-follow-jumps | CSE過(guò)程中跳過(guò)不會(huì)到達(dá)的目標(biāo) |
| -fcse-skip-blocks | CSE過(guò)程中可以跳過(guò)條件塊 |
| -fdelete-null-pointer-checks | 去掉不必要的null指針檢查 |
| -fexpensive-optimizations | 執(zhí)行一些“較昂貴”的優(yōu)化 |
| -fforce-mem | 在寄存器里保存內(nèi)存操作數(shù)(只限GCC 4.1) |
| -fgcse | 執(zhí)行一遍全局CSE(Common Subexpression Elimination) |
| -fgcse-lm | 在全局CSE時(shí)把裝載指令移到循環(huán)外面 |
| -fgcse-sm | 在全局CSE時(shí)把保存治療移到循環(huán)外面 |
| -foptimize-sibling-calls | 優(yōu)化有副作用的或尾遞歸的函數(shù)調(diào)用 |
| -fpeephole2 | 執(zhí)行機(jī)器相關(guān)的深度優(yōu)化 |
| -fregmove | Reassigns register numbers for maximum register tying |
| -freorder-blocks | 重新安排函數(shù)的基本塊,以便減少分支和提高代碼局部性 |
| -freorder-functions | 對(duì)于經(jīng)常調(diào)用或極少調(diào)用的函數(shù),使用特殊的text段重新安排函數(shù)的基本塊,以提高代碼局部性 |
| -frerun-cse-after-loop | 在循環(huán)優(yōu)化之后執(zhí)行一遍CSE |
| -frerun-loop-opt | 執(zhí)行2此循環(huán)優(yōu)化 |
| -fsched-interblock | 在基本塊間調(diào)度指令 |
| -fsched-spec | Schedules speculative execution of nonload instructions |
| -fschedule-insns | 重新安排指令以最小化執(zhí)行延遲 |
| -fschedule-insns2 | 執(zhí)行第2次schedule-insns |
| -fstrength-reduce | 用“廉價(jià)”的指令代替“昂貴”的指令 |
| -fstrict-aliasing | 通知編譯器使用最嚴(yán)格的別名規(guī)則(aliasing rules) |
| -fthread-jumps | 試圖重新安排跳轉(zhuǎn)指令的順序,成為執(zhí)行的順序 |
| -ftree-pre | 在SSA Trees上執(zhí)行partial redundancy elimination (PRE) |
| -funit-at-a-time | 在開(kāi)始代碼生成之前對(duì)整個(gè)文件進(jìn)行語(yǔ)法分析,以便進(jìn)行額外的優(yōu)化,比如重新安排代碼和申明,去掉從不引用的靜態(tài)變量和函數(shù)等。 |
| -fweb | 把每個(gè)web(代碼的存活范圍)賦給它自己的偽寄存器,方便后續(xù)的優(yōu)化,例如CSE,dead code elimination和循環(huán)優(yōu)化。 |
4個(gè)-falign-選項(xiàng)強(qiáng)制函數(shù)、跳轉(zhuǎn)指令、標(biāo)簽和循環(huán)對(duì)齊到2的指數(shù)邊界,原理是內(nèi)存對(duì)齊的數(shù)據(jù)和結(jié)構(gòu)對(duì)計(jì)算機(jī)有更高的訪問(wèn)速度。前提是對(duì)齊后的代碼被頻繁調(diào)用,能彌補(bǔ)因?qū)R造成的no-op指令的延遲。
-fcse-follow-jumps和-fcse-skip-blocks正如其名,是在前面介紹的CSE過(guò)程中執(zhí)行的優(yōu)化。使用-fcse-follow-jumps,CSE會(huì)跳過(guò)不可到達(dá)的目標(biāo)代碼。比如,下面的條件代碼:
if (i < 10) {
??? foo();
} else {
??? bar();
}
通常,即使(i < 10)測(cè)試為false,CSE仍要按照全路徑對(duì)foo()進(jìn)行優(yōu)化。如果你指定了-fcse-follow-jumps,CSE就直接跳到else塊進(jìn)行優(yōu)化(bar())。
-fcse-skip-blocks使CSE可以跳過(guò)條件塊。比如你寫了如下的if語(yǔ)句:
if (i >= 0) {
??? j = foo(i);
}
bar(j);
如果你指定了-fcse-skip-blocks而且i是負(fù)值,那么CSE將直接跳到bar(),越過(guò)了原來(lái)的if語(yǔ)句。而通常情況下,無(wú)論i是什么值,CSE都需要對(duì)if語(yǔ)句進(jìn)行處理。
-fpeephole2執(zhí)行CPU相關(guān)的深度優(yōu)化,把較長(zhǎng)的指令集替換成較短的、簡(jiǎn)練的指令。比如下面的代碼:
a = 2;
for (i = 1; i < 10; ++i)
a += 2;
GCC可能把整個(gè)循環(huán)替換成賦值語(yǔ)句a=20。使用了-fpeephole2,GCC就在標(biāo)準(zhǔn)的深度優(yōu)化(比如C語(yǔ)言里用位操作代替算術(shù)操作)之外還進(jìn)行CPU相關(guān)的優(yōu)化。
-fforce-mem是指在對(duì)指針進(jìn)行運(yùn)算前,把內(nèi)存操作數(shù)和常量復(fù)制到寄存器里,目的是生成內(nèi)存引用的common subexpressions,然后用CSE進(jìn)行優(yōu)化。前面已經(jīng)講過(guò),CSE能去除多余的寄存器裝載指令。
-foptimize-sibling-calls試圖優(yōu)化掉尾遞歸的或同屬調(diào)用(sibling call)的函數(shù)。尾遞歸調(diào)用是指函數(shù)的遞歸調(diào)用出現(xiàn)在最后面。比如下面的代碼:
int inc(int i)
{
??? printf("%d/n" i);
??? if(i < 10)
??????? inc(i + 1);
}
上面定義的inc()函數(shù),在函數(shù)體最后遞歸調(diào)用了以i+1為參數(shù)的自身,直到i大于等于10。既然尾遞歸調(diào)用的深度是已知的,那么就可以用一個(gè)迭代來(lái)消除尾遞歸。-foptimize-sibling-calls就試圖進(jìn)行這種優(yōu)化。同屬調(diào)用(sibling call)也是指函數(shù)調(diào)用出現(xiàn)在尾上下文(tail context,比如return語(yǔ)句)中。
GCC Optimizations for Code Size
選項(xiàng)-Os變得越來(lái)越流行,因?yàn)樗说?span style="font-family:Calibri">2級(jí)優(yōu)化里除增加代碼量以外的所有優(yōu)化。-Os還應(yīng)用了一些減少代碼量的額外優(yōu)化。代碼量在這里不是指程序文件在磁盤里占用的存儲(chǔ)空間,而是指程序運(yùn)行時(shí)占用的內(nèi)存空間。注意,-Os會(huì)自動(dòng)屏蔽下面的優(yōu)化選項(xiàng):
? -falign-functions
? -falign-jumps
? -falign-labels
? -falign-loops
? -fprefetch-loop-arrays
? -freorder-blocks
? -freorder-blocks-and-partition
? -ftree-ch
分別使用-O2和-Os編譯程序,然后對(duì)比它們的性能和內(nèi)存用量是很有意義的。比如,我發(fā)現(xiàn)最新的Linux內(nèi)核下使用-O2和-Os編譯的程序擁有幾乎相同的運(yùn)行時(shí)性能,但是后者的運(yùn)行時(shí)內(nèi)存用量卻少了15%。當(dāng)然,你的環(huán)境下可能有不同的發(fā)現(xiàn)。
Level 3 GCC Optimizations
指定-O3優(yōu)化選項(xiàng)除了包括第1級(jí),第2級(jí)的所有優(yōu)化外,還包括:
l? -fgcse-after-reload:在重新裝載時(shí)執(zhí)行一遍額外的load elimination;
l? -finline-functions:把所有的“簡(jiǎn)單”函數(shù)內(nèi)聯(lián)到調(diào)用者中;
l? -funswitch-loops:Moves branches with loop invariant conditions out of loops
注意
如果使用了多個(gè)-O選項(xiàng),最后的那個(gè)決定一切。所以,命令gcc -O3 foo.c bar.c -O0 -o baz將不執(zhí)行任何優(yōu)化,因?yàn)?span style="font-family:Calibri">-O0出現(xiàn)在最后。
Manual GCC Optimization Flags
除了前面講的幾個(gè)-O選項(xiàng)能進(jìn)行的優(yōu)化外,GCC還有幾個(gè)只能用-f指定的優(yōu)化選項(xiàng),如下表所示:
| Flag | Description |
| -fbounds-check | 對(duì)訪問(wèn)數(shù)組的索引進(jìn)行檢查 |
| -fdefault-inline | 把C++成員函數(shù)默認(rèn)為內(nèi)聯(lián) |
| -ffast-math | 設(shè)置: -fno-math-errno -funsafe-math-optimizations -fno-trapping-math 選項(xiàng) |
| -ffinite-math-only | 禁止檢查NaN和無(wú)窮大的參數(shù)或結(jié)果 |
| -ffloat-store | 禁止在寄存器里存儲(chǔ)浮點(diǎn)數(shù)值 |
| -fforce-addr | 在寄存器里存儲(chǔ)內(nèi)存常量 |
| -ffunction-cse | 在寄存器里存儲(chǔ)函數(shù)地址 |
| -finline | 把用inline關(guān)鍵字指定的函數(shù)內(nèi)聯(lián)展開(kāi) |
| -finline-functions | 在調(diào)用者里把簡(jiǎn)單的函數(shù)內(nèi)聯(lián) |
| -finline-limit=n | 指定內(nèi)聯(lián)函數(shù)的偽指令數(shù)不超過(guò)n |
| -fkeep-inline-functions | 保持內(nèi)聯(lián)函數(shù)仍為可調(diào)用的函數(shù) |
| -fkeep-static-consts | 保留用static const申明但從未引用過(guò)的變量 |
| -fmath-errno | 設(shè)置數(shù)學(xué)函數(shù)的errno執(zhí)行時(shí)成為單條指令 |
| -fmerge-all-constants | 把模塊間相同值的變量合并 |
| -ftrapping-math | Emits code that generates user-visible traps for FP operations |
| -ftrapv | 產(chǎn)生代碼捕捉有符號(hào)值的運(yùn)算溢出 |
| -funsafe-math-optimizations | 禁止對(duì)浮點(diǎn)操作進(jìn)行錯(cuò)誤檢查和一致性測(cè)試 |
上面列出的選項(xiàng)很多都有關(guān)浮點(diǎn)操作。在進(jìn)行這些效果不確定的優(yōu)化的同時(shí),優(yōu)化器會(huì)背離嚴(yán)格的ISO和/或IEEE標(biāo)準(zhǔn),尤其是對(duì)數(shù)學(xué)函數(shù)和浮點(diǎn)運(yùn)算。在浮點(diǎn)運(yùn)算量巨大的應(yīng)用里,這樣做可能有顯著的性能提升,但是代價(jià)就是放棄了對(duì)標(biāo)準(zhǔn)的遵守。在某些情況下,這種放棄是可以接受的,當(dāng)然最終決定權(quán)在你的手里。
注意
不是所有的GCC優(yōu)化選項(xiàng)都可以用這些標(biāo)志來(lái)控制。有些優(yōu)化選項(xiàng)是完全自動(dòng)進(jìn)行的,而且只對(duì)代碼進(jìn)行小的修改。只要你使用了-O,就不能禁止這些優(yōu)化。
Processor-Specific Optimizations
傳統(tǒng)上,為目標(biāo)機(jī)器定制的優(yōu)化并不被提倡,因?yàn)樗鼈円蕾囋S多目標(biāo)系統(tǒng)的信息。GCC利用這些信息產(chǎn)生特殊的代碼,使用處理器特有的屬性或避免已知的缺陷。
在寫這本書以前,我通常只用-O2來(lái)優(yōu)化我的程序,把剩下的事都交給編譯器。寫完這本書以后,我感覺(jué)自己的能力更強(qiáng)了,并給程序增加了一些被證明很有用的編譯選項(xiàng),即在特定情況下的特定優(yōu)化選項(xiàng)。下面的文字都是指導(dǎo)性的,因?yàn)楫吘鼓惚任腋约旱拇a。
Automating Optimization with Acovea
即使你把本文的內(nèi)容忘的差不多了,你肯定還是知道GCC的選項(xiàng)簡(jiǎn)直有無(wú)數(shù)個(gè)。要想給某一個(gè)特定的程序和特定的體系結(jié)構(gòu)選擇最好的GCC選項(xiàng)集,根本就是不可能的。所以很多人都做法是,使用標(biāo)準(zhǔn)的優(yōu)化選項(xiàng),然后在自己知道的其他選項(xiàng)里試驗(yàn)出幾個(gè)有用的。這樣做可能是為了節(jié)省開(kāi)發(fā)時(shí)間,但是它卻是“可恥”的。
Scott Ladd的Acovea程序(http://www.coyotegulch.com/products/acovea/index.html)提供了一個(gè)有趣而且有用的方法,獲得最好的優(yōu)化選擇,原理是利用進(jìn)化算法(evolutionary algorithm)模擬自然的選擇。聽(tīng)起來(lái)似乎很神奇,讓我們看看它是怎么工作的。Acovea應(yīng)用那些可能改善各種代碼的優(yōu)化算法,檢查結(jié)果,然后保留那些能使代碼性能提高的優(yōu)化。這和自然選擇過(guò)程的第一步在概念上非常相似。Acovea然后自動(dòng)把滿意的優(yōu)化算法傳給后續(xù)的選擇過(guò)程,這樣一步步應(yīng)用更多的優(yōu)化算法。
GCC專家們?cè)诰W(wǎng)上各處發(fā)表了很多進(jìn)化出“最佳”優(yōu)化的建議。不過(guò),這些建議可能是相互矛盾的,甚至在你的應(yīng)用里不產(chǎn)生效果。Acovea試圖通過(guò)反復(fù)的用優(yōu)化選項(xiàng)編譯代碼,并自動(dòng)詳細(xì)的分析性能,來(lái)得到最佳的結(jié)果。這種詳盡的分析經(jīng)歷可以使你學(xué)習(xí)GCC優(yōu)化選項(xiàng)中最難懂的部分——選項(xiàng)之間的相互影響。Acovea使你可以自動(dòng)測(cè)試所有的GCC選項(xiàng)組合,幫助你大大加快開(kāi)發(fā)過(guò)程,得到最少或編譯最快的程序版本。
Building Acovea
你可以從http://www.coyotegulch.com/products/acovea/index.html上得到Acovea的最新版本。安裝Acovea前需要2個(gè)額外的庫(kù):
l? coyotl:包含了Acovea用到的許多函數(shù),包括一個(gè)定制的隨機(jī)數(shù)生成器,底層的浮點(diǎn)應(yīng)用,一個(gè)通用的命令行分析器,和改良的排序和驗(yàn)證工具;
l? evocosm:提供了開(kāi)發(fā)進(jìn)化算法的一個(gè)框架。
然后使用簡(jiǎn)單的解壓、配置、安裝流程就行了。最后的可執(zhí)行程序runacovea位于/usr/local/bin(默認(rèn))下。
注意
Acovea只支持類Unix和Linux的系統(tǒng),對(duì)Cygwin的支持可能還需要點(diǎn)工作。
Configuring and Running Acovea
Acovea為你的程序進(jìn)行的測(cè)試選項(xiàng)定義在XML格式的配置文件里,配置文件的模板可以在http://www.coyotegulch.com/products/acovea/acovea-config.html得到。GCC的版本對(duì)這些配置文件非常重要,所以首先得到和你GCC版本相符的配置文件;其次是處理器的類型。下面是一個(gè)Acovea配置文件的范例:
<?xml version="1.0"?>
<acovea_config>
<acovea version="5.2.0" />
<description value="gcc 4.1 Opteron (AMD64/x86_64)" />
<quoted_options value="false" />
<prime command="gcc"
flags="-lrt -lm -std=gnu99 -O1 -march=opteron ACOVEA_OPTIONS
-o ACOVEA_OUTPUT ACOVEA_INPUT" />
<baseline description="-O1"
command="gcc"
flags="-lrt -lm -std=gnu99 -O1 -march=opteron -o ACOVEA_OUTPUT
ACOVEA_INPUT" />
<baseline description="-O2"
command="gcc"
flags="-lrt -lm -std=gnu99 -O2 -march=opteron -o ACOVEA_OUTPUT
ACOVEA_INPUT" />
<baseline description="-O3"
command="gcc"
flags="-lrt -lm -std=gnu99 -O3 -march=opteron -o ACOVEA_OUTPUT
ACOVEA_INPUT" />
<baseline description="-O3 -ffast-math"
command="gcc"
flags="-lrt -lm -std=gnu99 -O3 -march=opteron -ffast-math
-o ACOVEA_OUTPUT ACOVEA_INPUT" />
<baseline description="-Os"
command="gcc"
flags="-lrt -lm -std=gnu99 -Os -march=opteron -o ACOVEA_OUTPUT
ACOVEA_INPUT" />
<!-- A list of flags that will be "evolved" by ACOVEA (85 for GCC 4.1!) -->
<flags>
<!-- O1 options (these turn off options implied by -O1) -->
<flag type="simple" value="-fno-merge-constants" />
<flag type="simple" value="-fno-defer-pop" />
<flag type="simple" value="-fno-thread-jumps" />
<flag type="enum"
value="-fno-omit-frame-pointer|-momit-leaf-frame-pointer" />
<flag type="simple" value="-fno-guess-branch-probability" />
<flag type="simple" value="-fno-cprop-registers" />
<flag type="simple" value="-fno-if-conversion" />
. . ..
<!-- O2 options -->
<flag type="simple" value="-fcrossjumping" />
<flag type="simple" value="-foptimize-sibling-calls" />
<flag type="simple" value="-fcse-follow-jumps" />
<flag type="simple" value="-fcse-skip-blocks" />
<flag type="simple" value="-fgcse" />
<flag type="simple" value="-fexpensive-optimizations" />
<flag type="simple" value="-fstrength-reduce" />
<flag type="simple" value="-frerun-cse-after-loop" />
<flag type="simple" value="-frerun-loop-opt" />
…
<!-- O3 options -->
<flag type="simple" value="-fgcse-after-reload" />
<flag type="simple" value="-finline-functions" />
<flag type="simple" value="-funswitch-loops" />
…
<!-- Additional options -->
<flag type="simple" value="-ffloat-store" />
<flag type="simple" value="-fprefetch-loop-arrays" />
<flag type="simple" value="-fno-inline" />
<flag type="simple" value="-fpeel-loops" />
…
<!-- Tuning options that have a numeric value -->
<flag type="tuning" value="-finline-limit" default="600" min="100"
max="10000" step="100" separator="=" />
</flags>
</acovea_config>
注意
Acovea可以用于任何GCC編譯,只要給baseline屬性的command元素賦相應(yīng)的值就行了。
當(dāng)你準(zhǔn)備好了配置文件后,就可以使用runacovea程序進(jìn)行測(cè)試:
runacovea –config config-file-name –input source-file-name
注意
默認(rèn)情況下,Acovea把速度和性能放在首位,不過(guò)也可以指定-size選項(xiàng)使runacovea首先優(yōu)化代碼量。
執(zhí)行runacovea能得到很多的輸出,因?yàn)樗褂迷S多優(yōu)化的排列組合進(jìn)行測(cè)試,最終的輸出可能是如下樣子:
Acovea completed its analysis at 2005 Nov 24 08:45:34
Optimistic options:
-fno-defer-pop (2.551)
-fmerge-constants (1.774)
-fcse-follow-jumps (1.725)
-fthread-jumps (1.822)
Pessimistic options:
-fcaller-saves (-1.824)
-funswitch-loops (-1.581)
-funroll-loops (-2.262)
-fbranch-target-load-optimize2 (-2.31)
-fgcse-sm (-1.533)
-ftree-loop-ivcanon (-1.824)
-mfpmath=387 (-2.31)
-mfpmath=sse (-1.581)
Acovea's Best-of-the-Best:
gcc -lrt -lm -std=gnu99 -O1 -march=opteron -fno-merge-constants
-fno-defer-pop -momit-leaf-frame-pointer -fno-if-conversion
-fno-loop-optimize -ftree-ccp -ftree-dce -ftree-dominator-opts
-ftree-dse -ftree-copyrename -ftree-fre -ftree-ch -fmerge-constants
-fcrossjumping -fcse-follow-jumps -fpeephole2 -fschedule-insns2
-fstrict-aliasing -fthread-jumps -fgcse-lm -fsched-interblock -fsched-spec
-freorder-functions -funit-at-a-time -falign-functions -falign-jumps
-falign-loops -falign-labels -ftree-pre -finline-functions -fgcse-after-reload
-fno-inline -fpeel-loops -funswitch-loops -funroll-all-loops -fno-function-cse
-fgcse-las -ftree-vectorize -mno-push-args -mno-align-stringops
-minline-all-stringops -mfpmath=sse,387 -funsafe-math-optimizations
-finline-limit=600 -o /tmp/ACOVEAA7069796 fibonacci_all.c
Acovea's Common Options:
gcc -lrt -lm -std=gnu99 -O1 -march=opteron -fno-merge-constants
-fno-defer-pop -momit-leaf-frame-pointer -fcse-follow-jumps -fthread-jumps
-ftree-pre -o /tmp/ACOVEAAA635117 fibonacci_all.c
-O1:
gcc -lrt -lm -std=gnu99 -O1 -march=opteron -o /tmp/ACOVEA58D74660 fibonacci_all.c
-O2:
gcc -lrt -lm -std=gnu99 -O2 -march=opteron -o /tmp/ACOVEA065F6A10 fibonacci_all.c
-O3:
gcc -lrt -lm -std=gnu99 -O3 -march=opteron -o /tmp/ACOVEA934D7357 fibonacci_all.c
-O3 -ffast-math:
gcc -lrt -lm -std=gnu99 -O3 -march=opteron -ffast-math -o /tmp/ACOVEA408E67B6
fibonacci_all.c
-Os:
gcc -lrt -lm -std=gnu99 -Os -march=opteron -o /tmp/ACOVEAAB2E22A4 fibonacci_all.c
正如你所看到的,Acovea生成了一些最佳的優(yōu)化選項(xiàng)組合列表,還有一些建議的GCC優(yōu)化選項(xiàng)信息。
前面也說(shuō)過(guò),靠手動(dòng)詳盡的測(cè)試所有GCC優(yōu)化選項(xiàng)并找出它們之間的相互影響,需要相當(dāng)長(zhǎng)的時(shí)間。Acovea的最大作用就是自動(dòng)幫你來(lái)做這件事情。要更多的了解Acovea,請(qǐng)參考http://www.coyotegulch.com/products/acovea。總結(jié)
以上是生活随笔為你收集整理的Optimizing Code with GCC的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MFC的Main函数跑哪去了
- 下一篇: JM与h264标准中的关键字说明