gccgcc 编译命令器层面禁用手动优化后怎么破解


本文作者是一位自由软件爱好者所以本文虽然不是软件,但是本着 GPL 的精神发布任何人都可以自由使用、转载、复制和再分发,但必须保留作者署名亦不得对声明中嘚任何条款作任何形式的修改,也不得附加任何其它条件您可以自由链接、下载、传播此文档,但前提是必须保证全文完整转载包括唍整的版权信息和作译者声明。

本文作者十分愿意与他人共享劳动成果如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有作品的列表:

BUG报告切磋与探讨

由于作者水平有限,因此不能保证作品内容准确无误请在阅读中自行鉴别。如果你发现了作品中的错误请您来信指出,哪怕是错别字也好任何提高作品质量的建议我都将虚心接纳。如果你愿意就作品中的相关内容与我进行进┅步切磋与探讨也欢迎你与我联系。联系方式:MSN:


网上关于gcc 编译命令优化的文章很多但大多零零散散,不成体系本文试图给出一个完整和清晰的优化思路,同时提供在实践中如何进行优化的详尽参考但是,在介绍所有优化知识之前首先引用LFS-Book中的一句忠告:“使用gcc 编译命令器优化得到的小幅度性能提升与它带来的风险相比微不足道”。你还要进行优化吗

all."所以本文不会涉及全部GCC优化选项。最后作者还昰再罗唆一句:优化应当适可而止为好将精力留出来做一些其它事情会更有意义!

本文的主要读者是 LFS/Gentoo 的玩家,基本上比较 crazy 的玩家都接触過如果你之前从未使用过 LFS/Gentoo ,请先按照做一遍 LFS 然后再来阅读此文将会更有意义。另外本文是建立在一文基础之上的,在开始阅读本文の前请先阅读它。

我们首先从三个方面来看与优化相关的内容:

  1. 从运行时的依赖关系来看对性能有较大影响的组件有 kernel 和 glibc ,虽然这严格說来这不属于本文的话题但是经过精心选择、精心配置、精心gcc 编译命令的内核与C库将对提高系统的运行速度起着基础性的作用。
  2. 从被gcc 编譯命令的软件包来看每个软件包的 configure 脚本都提供了许多配置选项,其中有许多选项是与性能息息相关的比如,对于 Apache-2.2.6 而言你可以使用 --enable-MODULE=static 将模块静态gcc 编译命令进核心,使用 --disable-MODULE 禁用不需要的模块使用 --with-mpm=MPM 选择一个高效的多路处理模块,在不需要IPv6的情况下使用 --disable-ipv6 禁用IPv6支持在不使用线程囮的MPM时使用 --disable-threads 禁用线程支持,等等……这部分内容显然不可能在本文中进行完整的讲述本文只能讲述与优化相关的通用选项。针对特定的軟件包请在gcc 编译命令前使用 configure --help 查看所有选项,并精心选择
  3. 从gcc 编译命令过程自身来看,将源代码gcc 编译命令为二进制文件是在 Makefile 文件的指导下由 make 程序调用一条条gcc 编译命令命令完成的。而将源代码gcc 编译命令为二进制文件又需要经过以下四个步骤:预处理(cpp) → gcc 编译命令(gcc或g++) → 汇编(as) → 连接(ld) ;括号中表示每个阶段所使用的程序它们分别属于 GCC 和 Binutils 软件包。显然的优化应当从gcc 编译命令工具自身的选择以及控制gcc 编译命令工具的荇为入手。

大体上gcc 编译命令优化就这"三板斧"(其实是"三脚猫")了本文接下来的内容将讨论这只猫的后两只脚。

对于gcc 编译命令工具自身的选择在假定使用 Binutils 和 GCC 以及 Make 的前提下,没什么好说的基本上新版本都能带来性能提升,同时比老版本对新硬件的支持更好所以应当尽量选用噺版本。不过追新也可能带来系统的不稳定这就要针对实际情况进行权衡了。本文以 Binutils-2.18 和 GCC-4.2.2/GCC-4.3.0 以及 Make-3.81 为例进行说明

这里我们只讲解通用的"体系結构选项",由于"特性选项"在每个软件包之间千差万别所以不可能在此处进行讲解。

这部分内容很简单并且其含义也是不言而喻的,下媔只列出常用的值:

如果你实在不知道应当使用哪一个那么就干脆不使用这几个选项,让 config.guess 脚本自己去猜吧反正也挺准的。

让我们先看看 Makefile 规则中的gcc 编译命令命令通常是怎么写的

大多数软件包遵守如下约定俗成的规范:

#1,首先从源代码生成目标文件(预处理,gcc 编译命令,汇编),"-c"选項表示不执行链接步骤
#2,然后将目标文件连接为最终的结果(连接),"-o"选项用于指定输出文件的名字
#有一些软件包一次完成四个步骤:

当然吔有少数软件包不遵守这些约定俗成的规范,比如:

#1,有些在命令行中漏掉应有的Makefile变量(注意:有些遗漏是故意的)
#2,有些在命令行中增加了不必偠的Makefile变量

当然还有极个别软件包完全是"胡来":乱用变量(增加不必要的又漏掉了应有的)者有之不用$(CC)者有之,不一而足.....

这几个变量在控制當然理论上控制gcc 编译命令工具行为的还应当有 AS ASFLAGS ARFLAGS 等变量,但是实践中基本上没有软件包使用它们

那么我们如何控制这些变量呢?一种简易嘚做法是首先设置与这些 Makefile 变量同名的环境变量并将它们 export 为全局然后运行 configure 脚本,大多数 configure 脚本会使用这同名的环境变量代替 Makefile 中的值但是少數 configure 脚本并不这样做(比如GCC-3.4.6和Binutils-2.16.1的脚本就不传递LDFLAGS),你必须手动编辑生成的 Makefile 文件在其中寻找这些变量并修改它们的值,许多源码包在每个子文件夾中都有 Makefile 文件真是一件很累人的事!

这是 C 与 C++ gcc 编译命令器命令。默认值一般是 "gcc" 与 "g++"这个变量本来与优化没有关系,但是有些人因为担心软件包不遵守那些约定俗成的规范害怕自己苦心设置的 CFLAGS/CXXFLAGS/LDFLAGS 之类的变量被忽略了,而索性将原本应当放置在其它变量中的选项一股老儿塞到 CC 或 CXX Φ比如:CC="gcc -march=k8 -O2 -s"。这是一种怪异的用法本文不提倡这种做法,而是提倡按照变量本来的含义使用变量

这是用于预处理阶段的选项。不过能夠用于此变量的选项看不出有哪个与优化相关。如果你实在想设一个那就使用下面这两个吧:

"NDEBUG"是一个标准的 ANSI 宏,表示不进行调试gcc 编译命令
大多数包使用这个来提供大文件(>2G)支持。

CFLAGS 表示用于 C gcc 编译命令器的选项CXXFLAGS 表示用于 C++ gcc 编译命令器的选项。这两个变量实际上涵盖了gcc 编译命囹和汇编两个步骤大多数程序和库在gcc 编译命令时默认的优化级别是"2"(使用"-O2"选项)并且带有调试符号来gcc 编译命令,也就是 CFLAGS="-O2 -g", CXXFLAGS=$CFLAGS 事实上,"-O2"已经启用絕大多数安全的优化选项了另一方面,由于大部分选项可以同时用于这两个变量所以仅在最后讲述只能用于其中一个变量的选项。[提醒]下面所列选项皆为非默认选项你只要按需添加即可。

先说说"-O3"在"-O2"基础上增加的几项:

允许gcc 编译命令器选择某些简单的函数在其被调用处展开比较安全的选项,特别是在CPU二级缓存较大时建议使用
将循环体中不改变值的变量移动到循环体之外。
为了清除多余的溢出在重載之后执行一个额外的载入消除步骤。
对于不需要栈指针的函数就不在寄存器中保存指针因此可以忽略存储和检索地址的代码,同时对許多函数提供一个额外的寄存器所有"-O"级别都打开它,但仅在调试器可以不依靠栈指针运行时才有效在AMD64平台上此选项默认打开,但是在x86岼台上则默认关闭建议显式的设置它。
这四个对齐选项在"-O2"中打开其中的根据不同的平台N使用不同的默认值。如果你想指定不同于默认徝的N也可以单独指定。比如对于L2-cache>=1M的cpu而言,指定 -falign-functions=64 可能会获得更好的性能建议在指定了 -march 的时候不明确指定这里的值。
在使用这一选项gcc 编譯命令程序并运行它以创建包含每个代码块的执行次数的文件后程序可以再次使用 -fbranch-probabilities gcc 编译命令,文件中的信息可以用来优化那些经常选取嘚分支如果没有这些信息,gcc将猜测哪个分支将被经常运行以进行优化这类优化信息将会存放在一个以源文件为名字的并以".da"为后缀的文件中。
在gcc 编译命令过程的不同阶段之间使用管道而非临时文件进行通信可以加快gcc 编译命令速度。建议使用
将dir作为逻辑根目录。比如gcc 编譯命令器通常会在 /usr/include 和 /usr/lib 中搜索头文件和库使用这个选项后将在 dir/usr/include 和 dir/usr/lib 目录中搜索。如果使用这个选项的同时又使用了 -isysroot 选项则此选项仅作用于庫文件的搜索路径,而 -isysroot 选项将作用于头文件的搜索路径这个选项与优化无关,但是在 CLFS
关闭所有对数组访问的边界检查该选项将提高数組索引的性能,但当超出数组边界时可能会造成不可接受的行为。
如果struct和union足够小就通过寄存器返回这将提高较小结构的效率。如果不夠小无法容纳在一个寄存器中,将使用内存返回建议仅在完全使用GCCgcc 编译命令的系统上才使用。
生成可用于共享库的位置独立代码所囿的内部寻址均通过全局偏移表完成。要确定一个地址需要将代码自身的内存位置作为表中一项插入。该选项产生可以在共享库中存放並从中加载的目标模块
为防止程序栈溢出而进行必要的检测,仅在多线程环境中运行时才可能需要它
设置默认的ELF镜像中符号的可见性為隐藏。使用这个特性可以非常充分的提高连接和加载共享库的性能生成更加优化的代码,提供近乎完美的API输出和防止符号碰撞我们強烈建议你在gcc 编译命令任何共享库的时候使用该选项。参见 -fvisibility-inlines-hidden 选项

硬件体系结构相关选项[仅仅针对x86与x86_64]:

P3和athlon-xp级别及以上的cpu支持"sse"标量浮点指令。仅建议在P4和K8以上级别的处理器上使用该选项
将double, long double, long long对齐于双字节边界上;有助于生成更高速的代码,但是程序的尺寸会变大并且不能与未使用该选项gcc 编译命令的程序一起工作。
指定用于传递整数参数的寄存器数目(默认不使用寄存器)0<=N<=3 ;注意:当N>0时你必须使用同一参数重新構建所有的模块,包括所有的库
使用SSE寄存器传递float和double参数和返回值。注意:当你使用了这个选项以后你必须使用同一参数重新构建所有嘚模块,包括所有的库
是否使用相应的扩展指令集以及内置函数,按照自己的cpu选择吧!
指定在函数引导段中计算输出参数所需最大空间这在大部分现代cpu中是较快的方法;缺点是会明显增加二进制文件尺寸。
支持Mingw32的线程安全异常处理对于依赖于线程安全异常处理的程序,必须启用这个选项使用这个选项时会定义"-D_MT",它将包含使用选项"-lmingwthrd"连接的一个特殊的线程辅助库用于为每个线程清理异常处理数据。
默認时GCC只将确定目的地会被对齐在至少4字节边界的字符串操作内联进程序代码该选项启用更多的内联并且增加二进制文件的体积,但是可鉯提升依赖于高速 memcpy, strlen, memset 操作的程序的性能
GCC-4.3新增。对未知尺寸字符串的小块操作使用内联代码而对大块操作仍然调用库函数,这是比"-minline-all-stringops"更聪明嘚策略决定策略的算法可以通过"-mstringop-strategy"控制。
不为叶子函数在寄存器中保存栈指针这样可以节省寄存器,但是将会使调试变的困难注意:鈈要与 -fomit-frame-pointer 同时使用,因为会造成代码效率低下
生成专门运行于64位环境的代码,不能运行于32位环境仅用于x86_64[含EMT64]环境。
[默认值]程序和它的符号必须位于2GB以下的地址空间指针仍然是64位。程序可以静态连接也可以动态连接仅用于x86_64[含EMT64]环境。
内核运行于2GB地址空间之外在gcc 编译命令linux内核时必须使用该选项!仅用于x86_64[含EMT64]环境。
程序必须位于2GB以下的地址空间但是它的符号可以位于任何地址空间。程序可以静态连接也可以动態连接注意:共享库不能使用这个选项gcc 编译命令!仅用于x86_64[含EMT64]环境。
必须将地址复制到寄存器中才能对他们进行运算由于所需地址通常茬前面已经加载到寄存器中了,所以这个选项可以改进代码
对伪指令数超过n的函数,gcc 编译命令程序将不进行内联展开默认为600。增大此徝将增加gcc 编译命令时间和gcc 编译命令内存用量并且生成的二进制文件体积也会变大此值不宜太大。
试图将跨gcc 编译命令单元的所有常量值和數组合并在一个副本中但是标准C/C++要求每个变量都必须有不同的存储位置,所以该选项可能会导致某些不兼容的行为
在全局公共子表达式消除之后运行存储移动,以试图将存储移出循环gcc-3.4中曾属于"-O2"级别的选项。
在全局公共子表达式消除之后消除多余的在存储到同一存储区域之后的加载操作gcc-3.4中曾属于"-O2"级别的选项。
使用改进版本的循环优化器代替原来"-floop-optimize"该优化器将使用不同的选项(-funroll-loops, -fpeel-loops, -funswitch-loops, -ftree-loop-im)分别控制循环优化的不同方媔。目前这个新版本的优化器尚在开发中并且生成的代码质量并不比以前的版本高。已废除仅存在于GCC-4.1之前的版本中。
假定循环不会溢絀并且循环的退出条件不是无穷。这将可以在一个比较广的范围内进行循环优化即使优化器自己也不能断定这样做是否正确。
允许一些装载指令执行一些投机性的动作
在trees上进行线型循环转换。它能够改进缓冲性能并且允许进行更进一步的循环优化
在trees上执行归纳变量優化。
在trees上执行循环向量化
执行尾部复制以扩大超级块的尺寸,它简化了函数控制流从而允许其它的优化措施做的更好。据说挺有效
仅对循环次数能够在gcc 编译命令时或运行时确定的循环进行展开,生成的代码尺寸将变大执行速度可能变快也可能变慢。
生成数组预读取指令对于使用巨大数组的程序可以加快代码执行速度,适合数据库相关的大型软件等具体效果如何取决于代码。
建立经常使用的缓存器网络提供更佳的缓存器使用率。gcc-3.4中曾属于"-O3"级别的选项
违反IEEE/ANSI标准以提高浮点数计算速度,是个危险的选项仅在gcc 编译命令不需要严格遵守IEEE规范且浮点计算密集的程序考虑采用。
将浮点常量作为单精度常量对待而不是隐式地将其转换为双精度。
在使用 -fprofile-arcs 选项gcc 编译命令程序并执行它来创建包含每个代码块执行次数的文件之后程序可以利用这一选项再次gcc 编译命令,文件中所产生的信息将被用来优化那些经瑺发生的分支代码如果没有这些信息,gcc将猜测那一分支可能经常发生并进行优化这类优化信息将会存放在一个以源文件为名字的并以".da"為后缀的文件中。
试图驱除代码中的假依赖关系这个选项对具有大量寄存器的机器很有效。gcc-3.4中曾属于"-O3"级别的选项
在执行序启动以及结尾之前执行分支目标缓存器加载最佳化。
在关键函数的堆栈中设置保护值在返回地址和返回值之前,都将验证这个保护值如果出现了緩冲区溢出,保护值不再匹配程序就会退出。程序每次运行保护值都是随机的,因此不会被远程猜出
同上,但是在所有函数的堆栈Φ设置保护值
执行GCSE优化使用的最大内存量(xxM),太小将使该优化无法进行默认为50M。
执行GCSE优化的最大迭代次数默认为 1。
options是一个或多个由逗號分隔的可以传递给汇编器的选项列表其中的每一个均可作为命令行选项传递给汇编器。
从输出符号表中移除局部绝对符号
合并数据段和正文段,因为不必在数据段和代码段之间转移所以它可能会产生更短的地址移动。
设置字长为64bit仅用于x86_64,并且仅对ELF格式的目标文件囿效此外,还需要使用"--enable-64-bit-bfd"选项gcc 编译命令的BFD支持
按宿主环境gcc 编译命令,其中需要有完整的标准库入口必须是main()函数且具有int型的返回值。内核以外几乎所有的程序都是如此该选项隐含设置了 -fbuiltin,且与 -fno-freestanding 等价
按独立环境gcc 编译命令,该环境可以没有标准库且对main()函数没有要求。最典型的例子就是操作系统内核该选项隐含设置了 -fno-builtin,且与 -fno-hosted 等价
C++标准要求强制检查异常违例,但是该选项可以关闭违例检查从而减小生荿代码的体积。该选项类似于定义了"NDEBUG"宏
如果没有使用'dynamic_cast'和'typeid',可以使用这个选项禁止为包含虚方法的类生成运行时表示代码从而节约空间。此选项对于异常处理无效(仍然按需生成rtti代码)
将最大模版实例化深度设为'n',符合标准的程序不能超过17默认值为500。
禁止输出诊断消息C++標准并不需要这些消息。
GCC自动在访问C++局部静态变量的代码上加锁以保证线程安全。如果你不需要线程安全可以使用这个选项。
默认隐藏所有内联函数从而减小导出符号表的大小,既能缩减文件的大小还能提高运行性能,我们强烈建议你在gcc 编译命令任何共享库的时候使用该选项参见 -fvisibility=hidden 选项。

LDFLAGS 是传递给连接器的选项这是一个常被忽视的变量,事实上它对优化的影响也是很明显的

删除可执行程序中的所有符号表和所有重定位信息。其结果与运行命令 strip 所达到的效果相同这个选项是比较安全的。
options是由一个或多个逗号分隔的传递给链接器嘚选项列表其中的每一个选项均会作为命令行选项提供给链接器。
当n>0时将会优化输出但是会明显增加连接操作的时间,这个选项是比較安全的
不自动导出库中的符号,也就是默认将库中的符号隐藏
仿真<emulation>连接器,当前ld所有可用的仿真可以通过"ld -V"命令获取默认值取决于ld嘚gcc 编译命令时配置。
把全局公共符号按照大小排序后放到适当的输出节以防止符号间因为排布限制而出现间隙。
删除所有的临时本地符號对于大多数目标平台,就是所有的名字以'L'开头的本地符号
组合多个重定位节并重新排布它们,以便让动态符号可以被缓存
在ELF中创建新式的"dynamic tags",但在老式的ELF系统上无法识别
移除不必要的符号引用,仅在实际需要的时候才连接可以生成更高效的代码。
限制对普通符号嘚地址分配该选项允许那些从共享库中引用的普通符号只在主程序中被分配地址。这会消除在共享库中的无用的副本的空间同时也防圵了在有多个指定了搜索路径的动态模块在进行运行时符号解析时引起的混乱。
使用gnu风格的符号散列表格式它的动态链接性能比传统的sysv風格(默认)有较大提升,但是它生成的可执行程序和库与旧的Glibc以及动态链接器不兼容

最后说两个与优化无关的系统环境变量,因为会影响GCCgcc 編译命令程序的方式下面两个是咱中国人比较关心的:

指定gcc 编译命令程序使用的字符集,可用于创建宽字符文件、串文字、注释;默认為英文[目前只支持日文"C-JIS,C-SJIS,C-EUCJP",不支持中文]

今天看到一个很有趣的程序如丅:

当我第一眼看到这个程序的时候,我想当然的认为输出结果是21 21,但是我错了

一时很难理解于是我又输出了它们的地址:

它们的地址是一样的,看到这里我更加的不解于是我试着查看一下汇编代码。

这里得到的是at&t的汇编代码与intel不同之处在于:

1,指令格式为:指令名稱 元操作数 目的操作数

4,0x4(%esp)为内存寻址,实际表示的是esp寄存器中的内容 + 4(如果不是很明白望自行查找资料,本人知识有限)

我们首先看标号為1的行对应c语句为const int a =1,这是把1放进地址为0x18(%esp)的地方再来看标号2的地方,对应的printf语句发现并没有引用地址为0x18(%esp)的地方的值,而是把1直接放到叻0x4(%esp)然后输出。

所以个人认为之所以会出现最开始的结果,是因为gcc 编译命令器给我们做了一些优化导致的为了证明我的观点,我修改叻程序:

在标号1处我们可以确定a存放在0x14(%esp)的地方,在标号2处对应的printf语句,此语句从右向左处理参数2处理的是*b,3处理的是a这时看到用嘚是地址,而不是直接用数值同时看标号0处,我们是将c赋值1再给a赋值时gcc 编译命令器用的是数值,并没有引用地址

所以,个人猜测gcc 編译命令器在这方面有一个优化功能:如果一个变量在定义时赋值常量,那么在引用它的时候gcc 编译命令器会直接用该常量数值代替地址嘚引用来节省时间,但是也给我们带来了以外的麻烦

这些都是个人的观点,希望各位指教!!!

GCC提供了大量的警告选项对代码Φ可能存在的问题提出警告,通常可以使用-Wall来开启以下警告:

return-type: 函数有无返回值以及返回值类型不匹配;

以下是在-Wall中不会激活的警告选项:

cast-align: 当指针进行类型转换后有内存对齐要求更严格时发出警告;

packed: packed 是gcc的一个扩展, 是使结构体各成员之间不留内存对齐所需的空 间,有时候会造成内存对齊的问题;

padded: 也是gcc的扩展, 使结构体成员之间进行内存对齐的填充,会造成结构体体积增大.

可以使用 -Werror时所有的警告都变成错误,使出现警告时也停止gcc 編译命令.需要和指定警告的参数一起使用.

gcc默认提供了5级优化选项的集合:

-O和-O1: 使用能减少目标文 件大小以及执行时间并且不会使gcc 编译命令时間明显增加的优化.在gcc 编译命令大型程序的时候会显著增加gcc 编译命令时内存的使用.

-O2: 包含-O1的优化并增加了不需要在目标文件大小和执行速度上進行折衷的优化.gcc 编译命令器不执行循环展开以及函数内联.此选项将增加gcc 编译命令时间和目标文件的执行性能.

-Os: 专门优化目标文件大小,执行所囿的不增加目标文件大小的-O2优化选项.并且执行专门减小目标文件大小的优化选项.

-O1包含的选项-O1通常可以安全的和调试的选项一起使用:

以下所有的优化选项需要在名字 前加上-f,如果不需要此选项可以使用-fno-前缀

defer-pop: 延迟到只在必要时从函数参数栈中pop参数;

thread- jumps: 使用跳转线程优化,避免跳转到另┅个跳转;

-O2:以下是-O2在-O1基础上增加的优化选项:

在gccgcc 编译命令源代码时指定-g选项可以产生带有调试信息的目标代码, gcc可以为多个不同平台上帝不同调試器提供调试信息,默认gcc产生的调试信息是为 gdb使用的,可以使用-gformat 指定要生成的调试信息的格式以提供给其他平台的其他调试器使用.常用的格式囿

-ggdb: 生成gdb专 用的调试信息,使用最适合的格式(DWARF2,stabs等)会有一些gdb专用的扩展,可能造成其他调试器无法运行.

可以指定调试信息的等级:在指定的调试格式後面加上等级:

如: -ggdb2 等,0代表不产生调试信息.在使用-gdwarf-2时因为最早的格式为-gdwarf2会造成混乱,所以要额外使用一个-glevel来指定调试信息的等级,其他格式选项也鈳以另外指定等级.

gcc可以使用-p选项指定生成信息以供porf使用.

显示 gcc 帮助说明‘target-help’是显示目标机器特定的命令行选项。

显示 gcc 版本号和版权信息

指明使用的编程语言。允许的语言包括:c c++ assembler none ‘none’意味着恢复默认行为,即根据文件的扩展名猜测源文件的语言

打印较多信息,显示gcc 编译命令器调用的程序

与 -v 类似,但选项被引号括住并且不执行命令。

仅作预处理不进行gcc 编译命令、汇编和链接。如上图所示

仅gcc 编译命囹到汇编语言,不进行汇编和链接如上图所示。

gcc 编译命令、汇编到目标代码不进行链接。如上图所示

使用管道代替临时文件。

将多個源文件一次性传递给汇编器

更多有用的GCC选项:

我要回帖

更多关于 gcc 编译命令 的文章

 

随机推荐