如今的编译器愈来愈聪明,功能愈来愈强,从简单的函数内联,到复杂的寄存器分析,一系列代码革命使程序运行得愈来愈快。大多数时候,更快比更小重要,由于磁盘空间和内存都变得便宜了。可是在嵌入式系统里,更小和更快是同样重要的,因此把代码进行优化是很是有意义的工做。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。