Optimizing Code with GCC

Windows C++开发环境高效配置指南:MSVC与MinGW双路径实践 C++作为高性能系统编程的核心语言,其开发效率高度依赖于本地工具链的配置。理解编译器、构建系统和包管理器的工作原理,是搭建高效开发环境的基础。通过合理组合这些工具,开发者可以构建出既能满足项目特定需求,又能提升编码体验的个性化工作流。在Windows平台上,这一过程尤为关键,因为其原生环境与常见的类Unix开发模式存在差异。本文聚焦于C++环境配置中的两个核心方案:一是基于微软原生MSVC编译器与VS Code编辑器的轻量高性能组合,特别适合Windows原生应用与游戏开发;二是利用MSYS2提供类Unix 阅读详情

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

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

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

OPTIMIZATION AND DEBUGGING

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

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

编译器优化理论概览

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

优化编译器使用几种办法来决定哪些代码可被优化。一种是控制流分析(control flow analysis),即检查循环和其他控制语句,比如if-then和case,找出程序可能的执行路径,然后决定哪些执行路径可以被改进。另一个典型的优化技术是检查数据是怎样使用的,即数据流分析(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. 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,本章稍候会叙述。由于有了这些重大的改变,原来的通用优化等级-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是介于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。 
从零搭建现代C++开发环境:VSCode+CMake跨平台配置指南 C++作为高性能系统编程的核心语言,其开发环境的搭建是初学者和跨平台开发者面临的首要挑战。传统的集成开发环境(IDE)往往体积庞大、配置复杂,而现代开发流程更倾向于模块化、轻量化的工具链组合。本文聚焦于构建一个“干净”且高效的C++开发环境,其核心原理在于分离编辑器、编译器和构建系统,通过标准化接口实现跨平台协作。这种架构的技术价值在于提供了高度的灵活性和可维护性,开发者可以根据项目需求自由选型,同时保持开发体验的一致性。典型的应用场景包括学术研究、嵌入式系统前期开发、游戏引擎工具链搭建以及需要支持Wind 阅读详情

相关推荐

彻底解决Windows下Visual C++ 14.0缺失与Python包编译问题

在Windows平台进行软件开发或使用Python科学计算时,C++编译工具链和运行时库是基础依赖。其核心原理在于,许多高性能库和应用程序使用C/C++编写,需要通过编译器生成机器码,并依赖特定版本的运行时动态链接库(DLL)才能执行。对于开发者而言,正确配置这套环境是保障项目构建和软件运行的技术前提,尤其在数据科学和机器学习领域,涉及大量需要编译C扩展的Python包。本文聚焦于高频出现的“microsoft visual c++ 14.0 or greater is required”错误,深入解析其根

weixin_33947521的博客 316

GCC 术语表

 absolute address (绝对地址) 绝对地址是一个惟一的可用来指定某个内存字节的数字数值。参见相对地址。 address (地址) 参见绝对地址和相对地址。 aggregate (聚集) 包含多于一个基本数据类型的数据类型。例如,数组是一种聚集, C 的结构( struct )也是。 aliasing (别名机制) 同一内存地址可直接或间接地通过两个或多个...

weixin_33827731的博客 289

Windows平台C++开发环境配置全攻略:MSVC+VSCode+CMake实战指南

C++作为高性能系统开发的核心语言,其开发环境的搭建是项目启动和工程实践的首要步骤。环境配置的稳定性直接关系到编译效率、调试体验和团队协作。在Windows平台上,微软的MSVC编译器凭借与操作系统深度集成、对最新C++标准(如C++20/23)的快速支持以及卓越的调试体验,成为原生开发的首选。结合轻量、可扩展的Visual Studio Code编辑器,开发者可以获得高度定制化的编码环境。通过CMake这一事实标准的构建工具,可以实现跨平台的项目管理和自动化构建,有效管理多文件与第三方库依赖。本文以一份详

weixin_30338497的博客 526

CCS编译器优化

t 每个文件都可以设置编译器优化,右键.c文件-&gt;属性-&gt;optimization-&gt;optimization level 设置成空的,即可在debug的时候避免出现异常。开编译器优化可能导致跟踪函数的变量时值是错的。编译优化的好处是加快代码运行速度,但缺点就是只能把函数当做黑盒,函数内部的bebug结果是不可靠的。所以一般将算法和流程编到不同的文件中去,因为算法文件一般都是验证...

wssjn1994的博客 1万+

【ARM架构中文手册】第十七章 Optimizing Code to Run on ARM Processors

17.Optimizing Code to Run on ARM Processors 优化并不一定意味着使程序运行得更快。在嵌入式系统中,您可能更倾向于针对电池寿命、代码密度或内存占用进行优化。编写更高效的代码不仅可以提高性能,还对节省电池寿命至关重要。如果您能够在更少的周期内完成工作,就可以更长时间地关闭电源。 许多编译器提供了用于帮助优化的选项。例如,ARM编译器和GNU GCC编译器都具有 -O 标志,用于指定优化级别。 虽然处理器的技术参考手册(TRM)中可以找到周期时序信息,但即使是要计算一小

baron-周贺贺-代码改变世界ctw 229

空心杯电机在美容仪器中的应用:为什么你的洗脸刷动力这么强?

本文深入解析了空心杯电机如何成为高端美容仪器的核心动力源。通过对比传统电机,揭示了其无铁芯、低惯量、零齿槽效应的技术优势,从而实现了强劲动力、极致静音与细腻震动的用户体验。文章详细阐述了空心杯电机在洁面刷、导入仪等美容仪器中的具体应用与选型考量,并指出了开发中驱动匹配、测试等关键实战要点。

j2k3l4的博客 1013

GCC编译选项参数

GCC 编译选项参数 1 常用选项 2 预处理器选项 -includefile 在处理常规输入文件之前,首先处理文件file,其结果是,文件file的内容先得到编译.命令行上任何-D'和-U’选项永远在-includefile'之前处理,无论他们在命令行上的顺序如何.然而-include’和`-imacros’选项按书写顺序处理. -imacros file 在处理常规输入文件之前,首先处理文件file,但是忽略输出结果.由于丢弃了文件file的 输出内容, -imacrosfile'选项的唯一效果就是使

枫潇潇 3万+

Zynq 双核AMP 通信

4、配置离线编译(这里不讲),配置linux的内核地址空间(0x4000_0000改为0x2000_0000),以及单核启动。CPU1设置为0x2000_0000~0x3FFF_FFFF(也可以小于0x3FFF_FFFF)如果禁用CPU1的L2 cache,在CPU1的bsp里面添加编译时候所需要的宏。3、配置工程,将vivado产生的.xsa文件复制到amp_com项目目录下。9、使用petalinux-package --boot 打包。一、vivado工程。5、编译u-boot。

y183128184的博客 1088

c语言 o还是 0,c-gcc -O0仍然优化出“未使用”的代码。 是否有一个编译标志来改变它?...

正如我在这个问题中提到的那样,gcc正在删除(是,使用2949502265578750750)一行代码_mm_div_ss,大概是因为未保存结果。 但是,这将触发浮点异常并引发SIGFPE,如果删除该调用则不会发生。问题:是否有一个或多个标志传递给gcc,以便按原样编译代码? 我在想类似__warn_unused_result__的东西,但我没有看到类似的东西。 理想情况下,这将是一个编译器标志,...

weixin_39646107的博客 234

桂电 数电实验 期末考试 试卷+解析(74LS192 + 74LS153 + 74LS139 + 74LS00 / 74LS20)

桂电 数电实验 期末考试 试卷+解析(74LS192 + 74LS153 + 74LS139 + 74LS00 / 74LS20)

Molly 的博客 1万+

基于vue2的echarts3D地球

采用vue2+js实现了echarts3D地球的放大、缩小和旋转

qq_44699269的博客 1132

数据流图例子_针对GPU单指令多数据流的编译优化算法

1.单指令多数据流首先来看一段简单的if-else语句:if(A) { B = 1;//Instruction S1 C = 2;//Instruction S2 } else { B = 3;//Instruction S3 C = 4;//Instruction S4 } 假设代码中每条语句转换成指令后分别是S1、S2、S3、S4.如果在CPU的单指令单数据流中...

weixin_39547158的博客 567

在 Windows 系统下使用 VMware 安装 Ubuntu 24.04 LTS 系统

Ubuntu24.04桌面端官方下载:https://cn.ubuntu.com/download/desktop。

u014695938的博客 1万+

编译器 | 从工业实现到极简设计 —— 编译器的多元形态与实践

……

u013669912的博客 890

【黑马点评】项目知识点及面经整理

【黑马点评】项目知识点及面经整理

m0_52031708的博客 6605

低代码部署与管理——OpenClaw智能助手的应用部署与运维(2026技术版)

OpenClaw智能助手的低代码部署与管理平台采用分层架构设计,包含部署工具、环境管理、版本控制等核心模块,支持从开发到生产的全流程自动化部署。该系统通过Docker、Kubernetes等技术实现应用构建和部署,利用Terraform等工具管理多环境配置,并提供完善的监控运维能力。平台针对开发、测试、预生产和生产环境提供差异化配置,确保部署效率和运行可靠性。这种架构设计遵循自动化、可扩展性和安全性原则,显著提升了智能助手应用的部署效率和运维质量。

一键难忘的博客 319

用 Neon Intrinsics 优化 C 代码

以下内容翻译自:Optimizing C Code with Neon Intrinsics 概述 本指南向您展示如何在 C 或 C++ 代码中使用 Neon intrinsics 函数,以利用 Armv8架构中的 Advanced SIMD 技术。这些简单的示例演示了如何使用这些内嵌原语,并提供了一个解释其用途的机会。 希望使用 Advanced SIMD 技术的底层软件工程师、库编写人员和其他...

yiran103的专栏 4661

paddlehub无法安装,安装报错【Bug完美解决】

paddlehub无法安装,安装报错【Bug完美解决】 我们正在进行一个基于Python的项目开发,该项目需要集成PaddleHub,以利用其丰富的深度学习模型和工具。PaddleHub提供了一种简便的方式来使用预训练模型,以加速开发过程并提高模型效果。 现在paddlehub安装出现问题了,本文彻底解决paddlehub无法安装的问题。

一键难忘的博客 3388

Windows C++多线程开发:解决#include <thread>编译失败的完整指南

C++多线程编程是现代软件开发的核心技术之一,它允许程序同时执行多个任务,显著提升计算密集型应用的性能。其原理是通过操作系统提供的线程机制,在单个进程内创建多个执行流,共享内存空间但拥有独立的栈和寄存器状态。在C++11标准中,标准库正式引入了<thread>、<mutex>等头文件,为多线程编程提供了跨平台的统一接口,极大简化了并发程序的开发。然而,在实际工程实践中,特别是在Windows平台上,开发者常会遇到#include <thread>编译失败的典型问题。这通常源于编译器对C++标准的支持差异、项

weixin_30596165的博客 392

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

【亲测免费】 C++ 高性能编程第二版项目教程

C++ 高性能编程第二版项目教程 1. 项目介绍 项目概述 C++ High Performance Second Edition 是由 Packt Publishing 出版的一本关于优化 C++ 应用程序性能的书籍。该项目在 GitHub 上提供了一个代码仓库,包含了书中所有的示例代码和相关项目文件。通过这些代码,读者可以深入了解如何在不牺牲代码可读性的前提下,提高 C++ 应用程序的性能。 ...

gitblog_00061的博客 525

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

[RPi bring up] 深入树莓派内部,arm汇编语言精粹(上)​

thumb指令集是arm32指令集的一个子集,指令长度16bit,它的指令和32位arm指令是对应的,这样设计的目的是为了实现更高的代码密度code density。汇编的本质就是32bit二进制指令的助记符,一行一行的汇编指令对应的恰好是一个一个计算机能理解的32bit数字,我们需要用GNU Binutils的两个工具帮助从汇编生成二进制文件。以上是我对arm(应用程序)汇编的总结,比起高级语言,汇编本身就是一个不太友好的语言,所以本文略显罗嗦和乏味。知道的太少,不懂的太多。我们首先要理清几个概念。

sdfui32iruiwed的博客 401

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

在UltraEdit中配置C/C++编译环境:轻量级开发工作流实践

C/C++作为系统级编程语言,其开发环境配置是程序员的基础技能。传统IDE如Visual Studio功能全面但资源占用高,而轻量级编辑器如UltraEdit(UE)虽启动迅速,却缺乏原生编译能力。本文从编译器工作原理切入,解析如何通过环境变量配置与命令调用,将外部编译工具链(如MSVC)无缝集成到编辑器中。这种方案的技术价值在于实现编辑、编译、运行的快速闭环,尤其适合算法验证、脚本调试和小型项目维护。针对高频出现的“microsoft visual c++ 14.0 or greater is requi

weixin_34034670的博客 574
上一篇: Using GCC’s C++ Compiler
下一篇: Modern Core Wars
daidodo
博客等级 码龄22年 43粉丝 37原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值