Optimizing Code with GCC

现在的编译器越来越聪明,功能越来越强,从简单的函数内联,到复杂的寄存器分析,一系列代码革命使程序运行得越来越快。大多数时候,更快比更小重要,因为磁盘空间和内存都变得便宜了。但是在嵌入式系统里,更小和更快是一样重要的,所以把代码进行优化是非常有意义的工作。

如果你已经知道了怎样用gcc编译你的代码,现在是时候让你的代码更快或者更小了,这也是本章的内容。如果学有所成的话,你甚至可以让你的下一个程序既快又小。首先我们快速浏览一下编译器优化理论,然后讨论GCC的代码优化命令行选项,从一般的、体系结构无关的优化,到体系结构相关的优化方法。

虽然本章的示例代码都是C语言的,但是优化选项是通用的、语言无关的。能把一些优化选项适用到所有语言的编译器上,是一个编译器家族最大的优势,比如GCC编译器家族就是这样。

OPTIMIZATION AND DEBUGGING

没有代码优化的时候,GCC的一个重要目标是尽量缩短编译时间,并保证产生的代码在调试环境下的行为正确。比如,在优化过的代码里,一个变量如果在循环里多次计算,但是值其实没有变化,那么编译器可以把它移到循环的外面,只计算一次。虽然这是可行了(当然,只要不改变程序的运行结果),但是这使你无法按照源代码进行调试,因为计算该变量的代码被优化掉了。如果没有优化,你就可以正确的进行调试,检查变量的值。这就是所谓的“代码在调试环境下的行为正确”。

优化能改变代码的执行流程,但不改变执行结果。所以,优化一般是编码并调试完成之后才进行的。其实优化过的代码也是可以进行调试的,只是需要一些技巧。

编译器优化理论概览

代码优化是指分析一段编译后的代码,然后决定如何改变它,使它运行的更快,消耗的资源更少。拥有此功能的编译器叫做优化编译器(optimizing compilers),最后的输出代码叫做优化代码(optimized code)。

优化编译器使用几种办法来决定哪些代码可被优化。一种是控制流分析(control flow analysis),即检查循环和其他控制语句,比如if-thencase,找出程序可能的执行路径,然后决定哪些执行路径可以被改进。另一个典型的优化技术是检查数据是怎样使用的,即数据流分析(data flow analysis)。此法检查变量在哪里是怎样使用的(或没被使用),然后应用一系列的方程式到这些使用模式上面,从而找到优化的途径。

除了本章所述的一些计算机所作的改进外,优化还包括了对程序所使用的算法的改进。典型的比如冒泡排序算法改进成快速排序或希尔排序。这类改进能使程序的性能有本质的提高,比计算机能做的优化强得多,所以优化既是CPU的事情,也是人的事情。

本节定义一个基本块(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. UllmanAddison Wesley Longman, 1986. ISBN: 0-201-10088-6)。书里的优化理论介绍比本文详细的多,并且曾是我的启蒙书籍。

Code Motion

Code motion是一种优化技巧,是指在Common subexpression elimination(后有详述)时去掉多余的代码。Code motion并不是去掉所有的subexpression,而是在中间语言形式下改变它们的位置,以便能减少它们出现的次数。比如,在嵌套的循环或其他控制结构里,中间变量的计算次数可能不是最优的,要优化这些程序,编译器把这些计算语句移到循环更少的地方,并且保证计算结果是一样的。把计算移出循环的方法我们称为loop-invariant code motionCode 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;

后面的例子里,如果ij在后续的程序里没有用到的话,完全可以去除它们的定义。

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 motioncode 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 inliningloop unrolling都是指把全部或部分循环展开成一系列的直接指令。Function inlining是指用函数执行的指令替代对函数的调用。一般情况下,inlining能减少代码复杂度,提高性能,因为不需要多余的分支跳转。它还能给common subexpression eliminationcode 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把任何代码(无论什么级别)转化成目标代码之前都先翻译成这种代码。对于像GCCRTL这种非常低级的语言进行的优化也是很“低级”的,比如寄存器分配、堆栈和数据优化等。因为它很低级,所以不会像你想象的那样能进行数据类型、数组和变量引用、控制流改变等“高级”的优化。

GCC 4.0的作者们发明了一种新的中间形式static single assignment (SSA),通过对GCC编译器产生的语法分析树进行操作而得到,因此得名Tree SSA4.0及更高的GCC编译器在生成Tree SSA之前还有2种中间形式,叫做GENERICGIMPLEGENERIC是通过去除源代码中语言相关的结构得到的中间形式,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,本章稍候会叙述。由于有了这些重大的改变,原来的通用优化等级-O1-O2-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是介于03之间的整数;或者-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 propagationCCP)优化(只限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 propagationredundancy eliminationrange propagationexpression simplificationjump 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

执行一遍全局CSECommon 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

执行第2schedule-insns

-fstrength-reduce

用“廉价”的指令代替“昂贵”的指令

-fstrict-aliasing

通知编译器使用最严格的别名规则(aliasing rules

-fthread-jumps

试图重新安排跳转指令的顺序,成为执行的顺序

-ftree-pre

SSA Trees上执行partial redundancy elimination (PRE)

-funit-at-a-time

在开始代码生成之前对整个文件进行语法分析,以便进行额外的优化,比如重新安排代码和申明,去掉从不引用的静态变量和函数等。

-fweb

把每个web(代码的存活范围)赋给它自己的伪寄存器,方便后续的优化,例如CSEdead code elimination和循环优化。

4-falign-选项强制函数、跳转指令、标签和循环对齐到2的指数边界,原理是内存对齐的数据和结构对计算机有更高的访问速度。前提是对齐后的代码被频繁调用,能弥补因对齐造成的no-op指令的延迟。

-fcse-follow-jumps-fcse-skip-blocks正如其名,是在前面介绍的CSE过程中执行的优化。使用-fcse-follow-jumpsCSE会跳过不可到达的目标代码。比如,下面的条件代码:

if (i < 10) {

    foo();

} else {

    bar();

}

通常,即使(i < 10)测试为falseCSE仍要按照全路径对foo()进行优化。如果你指定了-fcse-follow-jumpsCSE就直接跳到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。使用了-fpeephole2GCC就在标准的深度优化(比如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-loopsMoves 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 LaddAcovea程序(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只支持类UnixLinux的系统,对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  
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Here are some possible ways to optimize the previous code: 1. Vectorize the calculations: Instead of using nested loops to compute the responsibility matrix, we can use vectorized operations to speed up the computation. For example, we can use broadcasting to compute the Euclidean distance between each pair of points in a matrix form. Similarly, we can use matrix multiplication to compute the weighted sums of the point clouds. ```python def em_for_alignment(xs: np.ndarray, ys: np.ndarray, num_iter: int = 10) -> Tuple[np.ndarray, np.ndarray]: """ The em algorithm for aligning two point clouds based on affine transformation :param xs: a set of points with size (N, D), N is the number of samples, D is the dimension of points :param ys: a set of points with size (M, D), M is the number of samples, D is the dimension of points :param num_iter: the number of EM iterations :return: ys_new: the aligned points: ys_new = ys @ affine + translation responsibility: the responsibility matrix P=[p(y_m | x_n)] with size (N, M), whose elements indicating the correspondence between the points """ # initialize the affine matrix and translation vector affine = np.eye(xs.shape[1]) translation = np.zeros(xs.shape[1]) # initialize the responsibility matrix responsibility = np.zeros((xs.shape[0], ys.shape[0])) for i in range(num_iter): # E-step: compute the responsibility matrix diff = xs[:, np.newaxis, :] - ys[np.newaxis, :, :] sq_dist = np.sum(diff ** 2, axis=-1) responsibility = np.exp(-0.5 * sq_dist) / (2 * np.pi) ** (xs.shape[1] / 2) responsibility /= np.sum(responsibility, axis=1, keepdims=True) # M-step: update the affine matrix and translation vector xs_weighted = responsibility.T @ xs ys_weighted = responsibility.T @ ys affine, _, _, _ = np.linalg.lstsq(xs_weighted, ys_weighted, rcond=None) translation = np.mean(ys, axis=0) - np.mean(xs @ affine, axis=0) # compute the aligned points ys_new = ys @ affine + translation return ys_new, responsibility ``` 2. Use the Kabsch algorithm: Instead of using the weighted least squares solution to update the affine matrix, we can use the Kabsch algorithm, which is a more efficient and numerically stable method for finding the optimal rigid transformation between two point clouds. The Kabsch algorithm consists of three steps: centering the point clouds, computing the covariance matrix, and finding the optimal rotation matrix. ```python def em_for_alignment(xs: np.ndarray, ys: np.ndarray, num_iter: int = 10) -> Tuple[np.ndarray, np.ndarray]: """ The em algorithm for aligning two point clouds based on affine transformation :param xs: a set of points with size (N, D), N is the number of samples, D is the dimension of points :param ys: a set of points with size (M, D), M is the number of samples, D is the dimension of points :param num_iter: the number of EM iterations :return: ys_new: the aligned points: ys_new = ys @ affine + translation responsibility: the responsibility matrix P=[p(y_m | x_n)] with size (N, M), whose elements indicating the correspondence between the points """ # center the point clouds xs_centered = xs - np.mean(xs, axis=0) ys_centered = ys - np.mean(ys, axis=0) # initialize the affine matrix and translation vector affine = np.eye(xs.shape[1]) translation = np.zeros(xs.shape[1]) # initialize the responsibility matrix responsibility = np.zeros((xs.shape[0], ys.shape[0])) for i in range(num_iter): # E-step: compute the responsibility matrix diff = xs_centered[:, np.newaxis, :] - ys_centered[np.newaxis, :, :] sq_dist = np.sum(diff ** 2, axis=-1) responsibility = np.exp(-0.5 * sq_dist) / (2 * np.pi) ** (xs.shape[1] / 2) responsibility /= np.sum(responsibility, axis=1, keepdims=True) # M-step: update the affine matrix and translation vector cov = xs_centered.T @ responsibility @ ys_centered u, _, vh = np.linalg.svd(cov) r = vh.T @ u.T t = np.mean(ys, axis=0) - np.mean(xs @ r, axis=0) affine = np.hstack((r, t[:, np.newaxis])) # compute the aligned points ys_new = ys @ affine[:, :-1] + affine[:, -1] return ys_new, responsibility ``` The Kabsch algorithm is more efficient than the weighted least squares solution, especially when the point clouds are high-dimensional or noisy. However, it only works for rigid transformations, i.e., rotations and translations. If the transformation between the point clouds is not rigid, we need to use a more general method, such as the Procrustes analysis or the Iterative Closest Point (ICP) algorithm.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值