Optimizing Code with GCC

如今的編譯器愈來愈聰明,功能愈來愈強,從簡單的函數內聯,到複雜的寄存器分析,一系列代碼革命使程序運行得愈來愈快。大多數時候,更快比更小重要,由於磁盤空間和內存都變得便宜了。可是在嵌入式系統裏,更小和更快是同樣重要的,因此把代碼進行優化是很是有意義的工做。html

若是你已經知道了怎樣用gcc編譯你的代碼,如今是時候讓你的代碼更快或者更小了,這也是本章的內容。若是學有所成的話,你甚至可讓你的下一個程序既快又小。首先咱們快速瀏覽一下編譯器優化理論,而後討論GCC的代碼優化命令行選項,從通常的、體系結構無關的優化,到體系結構相關的優化方法。node

雖然本章的示例代碼都是C語言的,可是優化選項是通用的、語言無關的。能把一些優化選項適用到全部語言的編譯器上,是一個編譯器家族最大的優點,好比GCC編譯器家族就是這樣。linux

OPTIMIZATION AND DEBUGGINGweb

沒有代碼優化的時候,GCC的一個重要目標是儘可能縮短編譯時間,並保證產生的代碼在調試環境下的行爲正確。好比,在優化過的代碼裏,一個變量若是在循環裏屢次計算,可是值其實沒有變化,那麼編譯器能夠把它移到循環的外面,只計算一次。雖然這是可行了(固然,只要不改變程序的運行結果),可是這使你沒法按照源代碼進行調試,由於計算該變量的代碼被優化掉了。若是沒有優化,你就能夠正確的進行調試,檢查變量的值。這就是所謂的「代碼在調試環境下的行爲正確」。算法

優化能改變代碼的執行流程,但不改變執行結果。因此,優化通常是編碼並調試完成以後才進行的。其實優化過的代碼也是能夠進行調試的,只是須要一些技巧。express

編譯器優化理論概覽編程

代碼優化是指分析一段編譯後的代碼,而後決定如何改變它,使它運行的更快,消耗的資源更少。擁有此功能的編譯器叫作優化編譯器(optimizing compilers),最後的輸出代碼叫作優化代碼(optimized code)。數組

優化編譯器使用幾種辦法來決定哪些代碼可被優化。一種是控制流分析(control flow analysis),即檢查循環和其餘控制語句,好比if-then和case,找出程序可能的執行路徑,而後決定哪些執行路徑能夠被改進。另外一個典型的優化技術是檢查數據是怎樣使用的,即數據流分析(data flow analysis)。此法檢查變量在哪裏是怎樣使用的(或沒被使用),而後應用一系列的方程式到這些使用模式上面,從而找到優化的途徑。性能優化

除了本章所述的一些計算機所做的改進外,優化還包括了對程序所使用的算法的改進。典型的好比冒泡排序算法改進成快速排序或希爾排序。這類改進能使程序的性能有本質的提升,比計算機能作的優化強得多,因此優化既是CPU的事情,也是人的事情。app

本節定義一個基本塊(basic block)指,只有一個入口和出口,其餘地方不包括終止、分支語句的連續代碼段。在一個基本塊內進行的轉化稱爲局部轉化(local transformations),一樣的不是在一個基本塊內的轉化稱爲全局轉化(global transformations)。一般編譯器會進行許多全局或局部的轉化,不過局部轉化老是先作。

雖然本節的例子使用C語言,其實全部GCC編譯器都使用一種中間語言進行這種轉化,這種中間語言比各類編程語言更適合計算機處理。GCC在產生最終的2進制代碼前,會使用一系列不一樣的中間語言翻譯你的源程序。不過對人類來講,C和其餘的高級語言比這些中間語言更好理解。

GCC編譯器還作了不少其餘的優化,有些很是細緻,甚至須要專業的編譯器理論知識。這裏列出的優化方法只是基礎,而且能用命令行來進行選擇。

注意

我所知的最經典的編譯器著做,當屬「龍書」《Compilers: Principles, Techniques, and Tools》,因其封面有一個恐龍而得名(Alfred V. Aho, Ravi Sethi, and Jeffrey D. Ullman,Addison Wesley Longman, 1986. ISBN: 0-201-10088-6)。書裏的優化理論介紹比本文詳細的多,而且曾是個人啓蒙書籍。

Code Motion

Code motion是一種優化技巧,是指在Common subexpression elimination(後有詳述)時去掉多餘的代碼。Code motion並非去掉全部的subexpression,而是在中間語言形式下改變它們的位置,以便能減小它們出現的次數。好比,在嵌套的循環或其餘控制結構裏,中間變量的計算次數可能不是最優的,要優化這些程序,編譯器把這些計算語句移到循環更少的地方,而且保證計算結果是同樣的。把計算移出循環的方法咱們稱爲loop-invariant code motion。Code motion還用在另外一種Common subexpression elimination裏,叫作partial redundancy elimination。

Common Subexpression Elimination

去除多餘的計算是一種標準的優化手段,由於它能減小程序的指令數,並獲得相同的結果。好比,若是一個表達式的參數(所引用的變量)值不變,那麼就能夠只計算一次結果,在之後引用該表達式的地方用結果值替代就能夠了。這些後來引用該表達式的地方就叫作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循環內的表達式j = 2 * STEP就是一個common subexpression,由於它的值能夠在進入循環以前計算,並且它的參數變量STEP(其實是一個宏定義)從不改變。Common subexpression elimination (CSE)把for循環內的重複計算去掉,成爲以下形式:

j = 2 * STEP;

for (i = 0; i < 100; ++i) {

    k = p[i] * j;

}

雖然這個例子簡單了點,不過能很好的說明問題,CSE去掉了100次對j的計算。CSE能去掉沒必要要的計算,改善程序性能,並減小了最終文件的大小。

Constant Folding

Constant folding是指去掉在編譯時就能肯定的數值計算表達式。這些表達式必須只包含常數值,或者值爲常數的變量。好比,下面的計算表達式均可以用一個賦值語句來替換:

n = 10 * 20 * 400;

 

i = 10;

j = 20;

ij = i * j;

後面的例子裏,若是i和j在後續的程序裏沒有用到的話,徹底能夠去除它們的定義。

Copy Propagation Transformations

這是另外一種減小或去除多餘計算的方法,即去除那些只是爲了傳遞數值的變量複製操做。看下面的代碼:

i = 10;

x[j] = i;

y[MIN] = b;

b = i;

Copy propagation transformation可能會優化成下面的代碼:

i = 10;

x[j] = 10;

y[MIN] = b;

b = 10;

在本例裏,copy propagation容許把右值變成常數,這樣比搜索和複製變量的值要快得多,而且也能去掉變量給本身賦值的狀況。有些狀況下,copy propagation並不直接產生優化,可是能簡化代碼,方便其餘優化,好比code motion和code elimination。

Constant propagation是一種把變量替換成常量的copy propagation transformation優化方法。Copy propagation主要指去掉沒必要要的變量間的相互複製,而Constant propagation則指去掉沒必要要的預約義值到變量的複製。

Dead Code Elimination

DCE是指把那些實際上無用的或多餘的代碼去掉。你可能想問「爲何我會寫這樣的代碼呢?」,其實在一個很大的、延續時間很長的項目裏,這是很容易發生的。許多DCE都是在中間語言表示的形式下進行的,由於它是源代碼更標準的翻譯,更容易發現沒必要要的中間計算。

Unreachable code elimination是指去除編譯器肯定不可能到達的代碼。好比下面的代碼塊:

if ( i == 10 ) {

    . . .

} else {

    . . .

    if ( i == 10) {

        . . .

    }

}

第2次對i是否等於10的測試及處理代碼能夠去掉,由於它是不可能到達的。UCE也是在中間代碼形式裏進行的。

If-Conversion

即分支重構,好比把大的if-then-elseif-else結構,重構成多個if語句,這樣能簡化代碼,爲之後的優化提供方便,並去除某些無用的跳轉和分支語句。

Inlining

即把複雜結構或函數調用替換成內聯的代碼以改善性能。Code inlining或loop unrolling都是指把所有或部分循環展開成一系列的直接指令。Function inlining是指用函數執行的指令替代對函數的調用。通常狀況下,inlining能減小代碼複雜度,提升性能,由於不須要多餘的分支跳轉。它還能給common subexpression elimination和code motion提供優化機會。這方面最經典的例子是Duff’s Device,詳見http://en.wikipedia.org/wiki/Duff's_device。

GCC Optimization Basics

GCC處理源代碼時,會把它轉化成一種中間形式。這樣作有幾大好處:、

l  把源代碼變得簡單、低級,使優化點暴露出來;

l  使可能很複雜的結構更容易生成簡單易讀的語法分析樹;

l  使用統一的中間形式使GCC編譯器之間能通用優化策略。

傳統上GCC使用的內部中間形式叫作Register Transfer Language (RTL),這是一種很低級的語言,GCC把任何代碼(不管什麼級別)轉化成目標代碼以前都先翻譯成這種代碼。對於像GCC的RTL這種很是低級的語言進行的優化也是很「低級」的,好比寄存器分配、堆棧和數據優化等。由於它很低級,因此不會像你想象的那樣能進行數據類型、數組和變量引用、控制流改變等「高級」的優化。

GCC 4.0的做者們發明了一種新的中間形式static single assignment (SSA),經過對GCC編譯器產生的語法分析樹進行操做而獲得,所以得名Tree SSA。4.0及更高的GCC編譯器在生成Tree SSA以前還有2種中間形式,叫作GENERIC和GIMPLE。GENERIC是經過去除源代碼中語言相關的結構獲得的中間形式,GIMPLE則是把GENERIC只讀地址引用進行簡化獲得。也許你也看出來了,在到達RTL等級以前,有許多的優化已經在這些相對高級點的層面上先作了。

關於Tree SSA的詳細信息和優化處理的步驟有許多資料可參考,其中一個是2003年的GCC開發者總結,網址爲http://www.linux.org.uk/~ajh/gcc/gccsummit-2003-proceedings.pdf。

What’s New in GCC 4.x Optimization

GCC 4.x家族最重要的變化是引入了中間形式Tree SSA,它提供了更多的優化空間,和更多的參數選項,包括-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,本章稍候會敘述。因爲有了這些重大的改變,原來的通用優化等級-O一、-O二、-O3和-Os都有了變化。除此之外,任何語言的GCC編譯器的優化都更廣泛了。

同時因爲有IBM的大力支持,GCC 4改進了向量化。向量化發現同一操做應用到多個數據的代碼,並改善其性能。GCC 4能夠把16個標量操做合成爲一個向量操做。這個優化方法能夠在遊戲、視頻和多媒體應用裏大展身手,由於這些程序的指令都是對數組向量的重複操做。

GCC 4還改進了數組邊界檢查和棧的內容結構檢查,保護程序免遭流行的緩衝區和棧溢出***。

Architecture-Independent Optimizations

GCC的優化分爲2大類:體系結構無關和體系結構相關。本節介紹體系結構無關的優化,包括計算機體系無關,好比x86;處理器類型無關,好比IA-32處理器;和處理器家族無關,例如Pentium IV (Xeon)。

GCC的優化選項有-O;-On,參數n是介於0到3之間的整數;或者-Os。-O0關閉優化。-O和-O1(又叫做第1級優化)等價,容許編譯器在不大量增長編譯時間的前提下減小代碼量和執行時間。-O2和-O3比-O1的優化等級更高,-Os會最小化代碼量。

本節全部的表格顯示了GCC提供的各類優化選項,若是要關閉相應優化,只需在-f和優化選項名字之間加上no-就好了。好比,要禁止deferred stack pops優化,命令行能夠這樣寫:

$ gcc myprog.c -o myprog -O1 -fno-defer-pop

注意

-f表示一個機器無關的操做標誌,即應用一個(大多數狀況下)體系結構無關的優化操做。這些標誌選項更改了GCC的默認行爲,可是不須要硬件的特殊支持。一般你能夠指定多個標誌。

Level 1 GCC Optimizations

下表列出了-O或-O1時進行的默認優化選項:

 

Optimization

Description

-fcprop-registers

試圖減小寄存器複製操做的次數

-fdefer-pop

Accumulates function arguments on the stack.

-fdelayed-branch

Utilizes instruction slots available after delayed branch instructions.

-fguess-branch-probability

利用隨機預測器猜想分支的可達性

-fif-conversion

把有條件跳轉變成非分支語句

-fif-conversion2

利用條件執行(要求CPU支持)進行if-conversion優化

-floop-optimize

應用幾個針對循環的優化

-fmerge-constants

合併多個模塊中相等的常量

-fomit-frame-pointer

省略函數楨指針的存儲。只能在不影響調試的系統裏激活

-ftree-ccp

在SSA Trees上進行較少的conditional constant propagation(CCP)優化(只限GCC 4.x)

-ftree-ch

在SSA Trees上執行loop header copying,即去掉一個跳轉指令,並提供code motion優化的機會(只限GCC 4.x)

-ftree-copyrename

在SSA Trees上執行copy renaming,即在複製位置把內部變量的名字改得更接近原始變量的名字(只限GCC 4.x)

-ftree-dce

在SSA Trees上執行dead code elimination (DCE)優化(只限GCC 4.x)

-ftree-dominator-opts

利用支配樹(dominator tree)遍從來進行一系列優化。A dominator tree is a tree where each node’s children are the nodes that it immediately dominates。這些優化包括constant/copy propagation,redundancy elimination,range propagation,expression simplification和jump threading(減小跳轉語句)(只限GCC 4.x)

-ftree-dse

在SSA Trees上執行dead store elimination (DSE)(只限GCC 4.x)

-ftree-fre

在SSA Trees上執行full redundancy elimination (FRE),即認爲全路徑計算的表達式會致使冗餘編譯。這和partial redundancy elimination(PRE)類似,不過比它快,找到的冗餘也比較少。(只限GCC 4.x)

-ftree-lrs

在SSA Trees轉化成RTL前,轉化成通常形式,並執行live range splitting。這種方法明確了變量的生存期,爲後續的優化提供幫助(只限GCC 4.x)

-ftree-sra

把聚合體替換成標量,即把對結構體的引用替換成標量數值,避免在沒必要要的時候把結構體提交到內存裏(只限GCC 4.x)

-ftree-ter

在SSA Trees轉化成RTL前,轉化成通常形式,並執行temporary expression replacement (TER)。把只使用一次的臨時表達式替換成原始定義的表達式,這樣更容易產生RTL代碼,並使產生的RTL代碼有更多的優化機會。(只限GCC 4.x)

第1級優化揉合了代碼大小和速度改進2種優化措施。好比,-tree-dce去掉了無用代碼,因而減小了代碼量;跳轉指令減小使整個程序的棧使用量減小;而-fcprop-registers是性能優化,減小在寄存器間複製數據的次數。

-fdelayed-branch和-fguess-branch-probability是指令調度改進。若是底層CPU支持指令調度,這些優化標誌就試圖使CPU等待下一條指令的等待時間最小化。

-floop-optimize開啓了對循環的優化,包括把常數表達式移出循環和簡化推出循環的條件測試。在更高的第2級優化裏,該標誌還執行strength reduction和循環展開。

-fomit-frame-pointer是很是有用,緣由有2個:省下了設置、保存和恢復楨指針的代碼;有時候省下了一個CPU寄存器,可有它用。而負面影響是:沒有了楨指針,調試(好比棧跟蹤,尤爲是嵌套很深的函數)變得很難甚至不可能。

-O2優化(第2級優化)包括了第1級的全部優化加上下表列出的另外一些優化。應用這些優化將延長編譯時間,不過你的程序性能將獲得顯著的提升。

Level 2 GCC Optimizations

當使用-O2優化選項時,下表的優化將默認進行:

Optimization

Description

-falign-functions

把函數對齊到2的指數字節邊界

-falign-jumps

把跳轉指令對齊到2的指數字節邊界

-falign-labels

把標籤對齊到2的指數字節邊界

-falign-loops

把循環對齊到2的指數字節邊界

-fcaller-saves

保存並恢復被函數調用改寫的寄存器

-fcrossjumping

分解等價代碼來減小代碼量

-fcse-follow-jumps

CSE過程當中跳過不會到達的目標

-fcse-skip-blocks

CSE過程當中能夠跳過條件塊

-fdelete-null-pointer-checks

去掉沒必要要的null指針檢查

-fexpensive-optimizations

執行一些「較昂貴」的優化

-fforce-mem

在寄存器裏保存內存操做數(只限GCC 4.1)

-fgcse

執行一遍全局CSE(Common Subexpression Elimination)

-fgcse-lm

在全局CSE時把裝載指令移到循環外面

-fgcse-sm

在全局CSE時把保存治療移到循環外面

-foptimize-sibling-calls

優化有反作用的或尾遞歸的函數調用

-fpeephole2

執行機器相關的深度優化

-fregmove

Reassigns register numbers for maximum register tying

-freorder-blocks

從新安排函數的基本塊,以便減小分支和提升代碼局部性

-freorder-functions

對於常常調用或極少調用的函數,使用特殊的text段從新安排函數的基本塊,以提升代碼局部性

-frerun-cse-after-loop

在循環優化以後執行一遍CSE

-frerun-loop-opt

執行2此循環優化

-fsched-interblock

在基本塊間調度指令

-fsched-spec

Schedules speculative execution of nonload instructions

-fschedule-insns

從新安排指令以最小化執行延遲

-fschedule-insns2

執行第2次schedule-insns

-fstrength-reduce

用「廉價」的指令代替「昂貴」的指令

-fstrict-aliasing

通知編譯器使用最嚴格的別名規則(aliasing rules)

-fthread-jumps

試圖從新安排跳轉指令的順序,成爲執行的順序

-ftree-pre

在SSA Trees上執行partial redundancy elimination (PRE)

-funit-at-a-time

在開始代碼生成以前對整個文件進行語法分析,以便進行額外的優化,好比從新安排代碼和申明,去掉從不引用的靜態變量和函數等。

-fweb

把每一個web(代碼的存活範圍)賦給它本身的僞寄存器,方便後續的優化,例如CSE,dead code elimination和循環優化。

4個-falign-選項強制函數、跳轉指令、標籤和循環對齊到2的指數邊界,原理是內存對齊的數據和結構對計算機有更高的訪問速度。前提是對齊後的代碼被頻繁調用,能彌補因對齊形成的no-op指令的延遲。

-fcse-follow-jumps和-fcse-skip-blocks正如其名,是在前面介紹的CSE過程當中執行的優化。使用-fcse-follow-jumps,CSE會跳過不可到達的目標代碼。好比,下面的條件代碼:

if (i < 10) {

    foo();

} else {

    bar();

}

一般,即便(i < 10)測試爲false,CSE仍要按照全路徑對foo()進行優化。若是你指定了-fcse-follow-jumps,CSE就直接跳到else塊進行優化(bar())。

-fcse-skip-blocks使CSE能夠跳過條件塊。好比你寫了以下的if語句:

if (i >= 0) {

    j = foo(i);

}

bar(j);

若是你指定了-fcse-skip-blocks並且i是負值,那麼CSE將直接跳到bar(),越過了原來的if語句。而一般狀況下,不管i是什麼值,CSE都須要對if語句進行處理。

-fpeephole2執行CPU相關的深度優化,把較長的指令集替換成較短的、簡練的指令。好比下面的代碼:

a = 2;

for (i = 1; i < 10; ++i)

a += 2;

GCC可能把整個循環替換成賦值語句a=20。使用了-fpeephole2,GCC就在標準的深度優化(好比C語言裏用位操做代替算術操做)以外還進行CPU相關的優化。

-fforce-mem是指在對指針進行運算前,把內存操做數和常量複製到寄存器裏,目的是生成內存引用的common subexpressions,而後用CSE進行優化。前面已經講過,CSE能去除多餘的寄存器裝載指令。

-foptimize-sibling-calls試圖優化掉尾遞歸的或同屬調用(sibling call)的函數。尾遞歸調用是指函數的遞歸調用出如今最後面。好比下面的代碼:

int inc(int i)

{

    printf("%d/n" i);

    if(i < 10)

        inc(i + 1);

}

上面定義的inc()函數,在函數體最後遞歸調用了以i+1爲參數的自身,直到i大於等於10。既然尾遞歸調用的深度是已知的,那麼就能夠用一個迭代來消除尾遞歸。-foptimize-sibling-calls就試圖進行這種優化。同屬調用(sibling call)也是指函數調用出如今尾上下文(tail context,好比return語句)中。

GCC Optimizations for Code Size

選項-Os變得愈來愈流行,由於它包含了第2級優化裏除增長代碼量之外的全部優化。-Os還應用了一些減小代碼量的額外優化。代碼量在這裏不是指程序文件在磁盤裏佔用的存儲空間,而是指程序運行時佔用的內存空間。注意,-Os會自動屏蔽下面的優化選項:

• -falign-functions

• -falign-jumps

• -falign-labels

• -falign-loops

• -fprefetch-loop-arrays

• -freorder-blocks

• -freorder-blocks-and-partition

• -ftree-ch

分別使用-O2和-Os編譯程序,而後對比它們的性能和內存用量是頗有意義的。好比,我發現最新的Linux內核下使用-O2和-Os編譯的程序擁有幾乎相同的運行時性能,可是後者的運行時內存用量卻少了15%。固然,你的環境下可能有不一樣的發現。

Level 3 GCC Optimizations

指定-O3優化選項除了包括第1級,第2級的全部優化外,還包括:

l  -fgcse-after-reload:在從新裝載時執行一遍額外的load elimination;

l  -finline-functions:把全部的「簡單」函數內聯到調用者中;

l  -funswitch-loops:Moves branches with loop invariant conditions out of loops

注意

若是使用了多個-O選項,最後的那個決定一切。因此,命令gcc -O3 foo.c bar.c -O0 -o baz將不執行任何優化,由於-O0出如今最後。

Manual GCC Optimization Flags

除了前面講的幾個-O選項能進行的優化外,GCC還有幾個只能用-f指定的優化選項,以下表所示:

Flag

Description

-fbounds-check

對訪問數組的索引進行檢查

-fdefault-inline

把C++成員函數默認爲內聯

-ffast-math

設置:

-fno-math-errno

-funsafe-math-optimizations

-fno-trapping-math

選項

-ffinite-math-only

禁止檢查NaN和無窮大的參數或結果

-ffloat-store

禁止在寄存器裏存儲浮點數值

-fforce-addr

在寄存器裏存儲內存常量

-ffunction-cse

在寄存器裏存儲函數地址

-finline

把用inline關鍵字指定的函數內聯展開

-finline-functions

在調用者裏把簡單的函數內聯

-finline-limit=n

指定內聯函數的僞指令數不超過n

-fkeep-inline-functions

保持內聯函數仍爲可調用的函數

-fkeep-static-consts

保留用static const申明但從未引用過的變量

-fmath-errno

設置數學函數的errno執行時成爲單條指令

-fmerge-all-constants

把模塊間相同值的變量合併

-ftrapping-math

Emits code that generates user-visible traps for FP operations

-ftrapv

產生代碼捕捉有符號值的運算溢出

-funsafe-math-optimizations

禁止對浮點操做進行錯誤檢查和一致性測試

上面列出的選項不少都有關浮點操做。在進行這些效果不肯定的優化的同時,優化器會背離嚴格的ISO和/或IEEE標準,尤爲是對數學函數和浮點運算。在浮點運算量巨大的應用裏,這樣作可能有顯著的性能提高,可是代價就是放棄了對標準的遵照。在某些狀況下,這種放棄是能夠接受的,固然最終決定權在你的手裏。

注意

不是全部的GCC優化選項均可以用這些標誌來控制。有些優化選項是徹底自動進行的,並且只對代碼進行小的修改。只要你使用了-O,就不能禁止這些優化。

Processor-Specific Optimizations

傳統上,爲目標機器定製的優化並不被提倡,由於它們依賴許多目標系統的信息。GCC利用這些信息產生特殊的代碼,使用處理器特有的屬性或避免已知的缺陷。

在寫這本書之前,我一般只用-O2來優化個人程序,把剩下的事都交給編譯器。寫完這本書之後,我感受本身的能力更強了,並給程序增長了一些被證實頗有用的編譯選項,即在特定狀況下的特定優化選項。下面的文字都是指導性的,由於畢竟你比我更懂本身的代碼。

Automating Optimization with Acovea

即便你把本文的內容忘的差很少了,你確定仍是知道GCC的選項簡直有無數個。要想給某一個特定的程序和特定的體系結構選擇最好的GCC選項集,根本就是不可能的。因此不少人都作法是,使用標準的優化選項,而後在本身知道的其餘選項裏試驗出幾個有用的。這樣作多是爲了節省開發時間,可是它倒是「可恥」的。

Scott Ladd的Acovea程序(http://www.coyotegulch.com/products/acovea/index.html)提供了一個有趣並且有用的方法,得到最好的優化選擇,原理是利用進化算法(evolutionary algorithm)模擬天然的選擇。聽起來彷佛很神奇,讓咱們看看它是怎麼工做的。Acovea應用那些可能改善各類代碼的優化算法,檢查結果,而後保留那些能使代碼性能提升的優化。這和天然選擇過程的第一步在概念上很是類似。Acovea而後自動把滿意的優化算法傳給後續的選擇過程,這樣一步步應用更多的優化算法。

GCC專家們在網上各處發表了不少進化出「最佳」優化的建議。不過,這些建議多是相互矛盾的,甚至在你的應用裏不產生效果。Acovea試圖經過反覆的用優化選項編譯代碼,並自動詳細的分析性能,來獲得最佳的結果。這種詳盡的分析經歷可使你學習GCC優化選項中最難懂的部分——選項之間的相互影響。Acovea使你能夠自動測試全部的GCC選項組合,幫助你大大加快開發過程,獲得最少或編譯最快的程序版本。

Building Acovea

你能夠從http://www.coyotegulch.com/products/acovea/index.html上獲得Acovea的最新版本。安裝Acovea前須要2個額外的庫:

l  coyotl:包含了Acovea用到的許多函數,包括一個定製的隨機數生成器,底層的浮點應用,一個通用的命令行分析器,和改良的排序和驗證工具;

l  evocosm:提供了開發進化算法的一個框架。

而後使用簡單的解壓、配置、安裝流程就好了。最後的可執行程序runacovea位於/usr/local/bin(默認)下。

注意

Acovea只支持類Unix和Linux的系統,對Cygwin的支持可能還須要點工做。

Configuring and Running Acovea

Acovea爲你的程序進行的測試選項定義在XML格式的配置文件裏,配置文件的模板能夠在http://www.coyotegulch.com/products/acovea/acovea-config.html獲得。GCC的版本對這些配置文件很是重要,因此首先獲得和你GCC版本相符的配置文件;其次是處理器的類型。下面是一個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元素賦相應的值就好了。

當你準備好了配置文件後,就可使用runacovea程序進行測試:

runacovea –config config-file-name –input source-file-name

注意

默認狀況下,Acovea把速度和性能放在首位,不過也能夠指定-size選項使runacovea首先優化代碼量。

執行runacovea能獲得不少的輸出,由於它使用許多優化的排列組合進行測試,最終的輸出多是以下樣子:

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生成了一些最佳的優化選項組合列表,還有一些建議的GCC優化選項信息。

前面也說過,靠手動詳盡的測試全部GCC優化選項並找出它們之間的相互影響,須要至關長的時間。Acovea的最大做用就是自動幫你來作這件事情。要更多的瞭解Acovea,請參考http://www.coyotegulch.com/products/acovea。
相關文章
相關標籤/搜索