GCC 编译优化指南

您所在的位置:网站首页 c编译链接原理 GCC 编译优化指南

GCC 编译优化指南

2023-03-10 05:47| 来源: 网络整理| 查看: 265

转自:html

http://www.jinbuguo.com/linux/optimize_guide.htmllinux

GCC 编译优化指南 做者:金步国[www.jinbuguo.com] 版权声明

本文做者是一位开源理念的坚决支持者,因此本文虽然不是软件,可是遵守开源的精神发布。web

无担保:本文做者不保证做品内容准确无误,亦不承担任何因为使用此文档所致使的损失。 自由使用:任何人均可以自由的阅读/连接/打印此文档,无需任何附加条件。 名誉权:任何人均可以自由的转载/引用/再创做此文档,但必须保留做者署名并注明出处。 其余做品

本文做者十分愿意与他人分享劳动成果,若是你对个人其余翻译做品或者技术文章有兴趣,能够在以下位置查看现有的做品集:算法

金步国做品集 [ http://works.jinbuguo.com/ ] 联系方式

因为做者水平有限,所以不能保证做品内容准确无误。若是你发现了做品中的错误,请您来信指出,哪怕是错别字也好,任何提升做品质量的建议我都将虚心接纳。数据库

Email(QQ):70171448在QQ邮箱 前言

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

%@&#=^%~*# ... OK, crazy guy! Let's Go!!缓存

在继续以前,做者仍是奉劝各位:若是追求极致的优化,那么它将是一件既耗时又麻烦的事情,你会陷入无止尽的测试、测试、再测试……另外 Gentoo wiki 上有这么一句话:"GCC has well over a hundred individual optimization flags and it would be insane to try and describe them all."因此本文不会涉及所有GCC优化选项。最后做者仍是再罗唆一句:优化应当适可而止为好,将精力留出来作一些其它事情会更有意义!安全

先决条件

本文的主要读者是 LFS/Gentoo 的玩家,基本上比较 crazy 的玩家都接触过,若是你以前从未使用过 LFS/Gentoo ,请先按照《Linux From Scratch 6.2 中文版》作一遍 LFS ,而后再来阅读此文将会更有意义。另外,本文是创建在《深刻理解软件包的配置、编译与安装》一文基础之上的,在开始阅读本文以前,请先阅读它。网络

基本原理

咱们首先从三个方面来看与优化相关的内容:多线程

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

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

编译工具的选择

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

configure 选项

这里咱们只讲解通用的"体系结构选项",因为"特性选项"在每一个软件包之间千差万别,因此不可能在此处进行讲解。

这部份内容很简单,而且其含义也是不言而喻的,下面只列出经常使用的值:

i586-pc-linux-gnu i686-pc-linux-gnu x86_64-pc-linux-gnu powerpc-unknown-linux-gnu powerpc64-unknown-linux-gnu

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

编译选项

让咱们先看看 Makefile 规则中的编译命令一般是怎么写的。

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

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

固然也有少数软件包不遵照这些约定俗成的规范,好比:

#1,有些在命令行中漏掉应有的Makefile变量(注意:有些遗漏是故意的) $(CC) $(CFLAGS) example.c -c -o example.o $(CC) $(CPPFLAGS) example.c -c -o example.o $(CC) example.o -o example $(CC) example.c -o example #2,有些在命令行中增长了没必要要的Makefile变量 $(CC) $(CFLAGS) $(LDFLAGS) example.o -o example $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) example.c -c -o example.o

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

尽管将源代码编译为二进制文件的四个步骤由不一样的程序(cpp,gcc/g++,as,ld)完成,可是事实上 cpp, as, ld 都是由 gcc/g++ 进行间接调用的。换句话说,控制了 gcc/g++ 就等于控制了全部四个步骤。从 Makefile 规则中的编译命令能够看出,编译工具的行为全靠 CC/CXX CPPFLAGS CFLAGS/CXXFLAGS LDFLAGS 这几个变量在控制。固然理论上控制编译工具行为的还应当有 AS ASFLAGS ARFLAGS 等变量,可是实践中基本上没有软件包使用它们。

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

CC 与 CXX

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

CPPFLAGS

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

-DNDEBUG "NDEBUG"是一个标准的 ANSI 宏,表示不进行调试编译。 -D_FILE_OFFSET_BITS=64 大多数包使用这个来提供大文件(>2G)支持。 CFLAGS 与 CXXFLAGS

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

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

-finline-functions 容许编译器选择某些简单的函数在其被调用处展开,比较安全的选项,特别是在CPU二级缓存较大时建议使用。 -funswitch-loops 将循环体中不改变值的变量移动到循环体以外。 -fgcse-after-reload 为了清除多余的溢出,在重载以后执行一个额外的载入消除步骤。

另外:

-fomit-frame-pointer 对于不须要栈指针的函数就不在寄存器中保存指针,所以能够忽略存储和检索地址的代 码,同时对许多函数提供一个额外的寄存器。全部"-O"级别都打开它,但仅在调试器能够不依靠栈指针运行时才有效。在AMD64平台上此选项默认打开,但 是在x86平台上则默认关闭。建议显式的设置它。 -falign-functions=N -falign-jumps=N -falign-loops=N -falign-labels=N 这 四个对齐选项在"-O2"中打开,其中的根据不一样的平台N使用不一样的默认值。若是你想指定不一样于默认值的N,也能够单独指定。好比,对于L2- cache>=1M的cpu而言,指定 -falign-functions=64 可能会得到更好的性能。建议在指定了 -march 的时候不明确指定这里的值。

调试选项:

-fprofile-arcs 在使用这一选项编译程序并运行它以建立包含每一个代码块的执行次数的文件后,程序能够再次使用 -fbranch-probabilities 编译,文件中的信息能够用来优化那些常常选取的分支。若是没有这些信息,gcc将猜想哪一个分支将被常常运行以进行优化。这类优化信息将会存放在一个以源文 件为名字的并以".da"为后缀的文件中。

全局选项:

-pipe 在编译过程的不一样阶段之间使用管道而非临时文件进行通讯,能够加快编译速度。建议使用。

目录选项:

--sysroot=dir 将dir做为逻辑根目录。好比编译器一般会在 /usr/include 和 /usr/lib 中搜索头文件和库,使用这个选项后将在 dir/usr/include 和 dir/usr/lib 目录中搜索。若是使用这个选项的同时又使用了 -isysroot 选项,则此选项仅做用于库文件的搜索路径,而 -isysroot 选项将做用于头文件的搜索路径。这个选项与优化无关,可是在 CLFS 中有着神奇的做用。

代码生成选项:

-fno-bounds-check 关闭全部对数组访问的边界检查。该选项将提升数组索引的性能,但当超出数组边界时,可能会形成不可接受的行为。 -freg-struct-return 若是struct和union足够小就经过寄存器返回,这将提升较小结构的效率。若是不够小,没法容纳在一个寄存器中,将使用内存返回。建议仅在彻底使用GCC编译的系统上才使用。 -fpic 生成可用于共享库的位置独立代码。全部的内部寻址均经过全局偏移表完成。要肯定一个地址,须要将代码自身的内存位置做为表中一项插入。该选项产生能够在共享库中存放并从中加载的目标模块。 -fstack-check 为防止程序栈溢出而进行必要的检测,仅在多线程环境中运行时才可能须要它。 -fvisibility=hidden 设置默认的ELF镜像中符号的可见性为隐藏。使用这个特性能够很是充分的提升链接和加 载共享库的性能,生成更加优化的代码,提供近乎完美的API输出和防止符号碰撞。咱们强烈建议你在编译任何共享库的时候使用该选项。参见 -fvisibility-inlines-hidden 选项。

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

-march=cpu-type 为特定的cpu-type编译二进制代码(不能在更低级别的cpu上运行)。Intel能够 用:pentium2, pentium3(=pentium3m), pentium4(=pentium4m), pentium-m, prescott, nocona, core2(GCC-4.3新增) 。AMD能够用:k6-2(=k6-3), athlon(=athlon-tbird), athlon-xp(=athlon-mp), k8(=opteron=athlon64=athlon-fx) -mfpmath=sse P3和athlon-xp级别及以上的cpu支持"sse"标量浮点指令。仅建议在P4和K8以上级别的处理器上使用该选项。 -malign-double 将double, long double, long long对齐于双字节边界上;有助于生成更高速的代码,可是程序的尺寸会变大,而且不能与未使用该选项编译的程序一块儿工做。 -m128bit-long-double 指定long double为128位,pentium以上的cpu更喜欢这种标准,而且符合x86-64的ABI标准,可是却不附合i386的ABI标准。 -mregparm=N 指定用于传递整数参数的寄存器数目(默认不使用寄存器)。00时将会优化输出,可是会明显增长链接操做的时间,这个选项是比较安全的。 -Wl,--exclude-libs=ALL 不自动导出库中的符号,也就是默认将库中的符号隐藏。 -Wl,-m 仿真链接器,当前ld全部可用的仿真能够经过"ld -V"命令获取。默认值取决于ld的编译时配置。 -Wl,--sort-common 把全局公共符号按照大小排序后放到适当的输出节,以防止符号间由于排布限制而出现间隙。 -Wl,-x 删除全部的本地符号。 -Wl,-X 删除全部的临时本地符号。对于大多数目标平台,就是全部的名字以'L'开头的本地符号。 -Wl,-zcomberloc 组合多个重定位节并从新排布它们,以便让动态符号能够被缓存。 -Wl,--enable-new-dtags 在ELF中建立新式的"dynamic tags",但在老式的ELF系统上没法识别。 -Wl,--as-needed 移除没必要要的符号引用,仅在实际须要的时候才链接,能够生成更高效的代码。 -Wl,--no-define-common 限制对普通符号的地址分配。该选项容许那些从共享库中引用的普通符号只在主程序中被分配地址。这会消除在共享库中的无用的副本的空间,同时也防止了在有多个指定了搜索路径的动态模块在进行运行时符号解析时引发的混乱。 -Wl,--hash-style=gnu 使用gnu风格的符号散列表格式。它的动态连接性能比传统的sysv风格(默认)有较大提高,可是它生成的可执行程序和库与旧的Glibc以及动态连接器不兼容。

最后说两个与优化无关的系统环境变量,由于会影响GCC编译程序的方式,下面两个是咱中国人比较关心的:

LANG 指定编译程序使用的字符集,可用于建立宽字符文件、串文字、注释;默认为英文。[目前只支持日文"C-JIS,C-SJIS,C-EUCJP",不支持中文] LC_ALL 指定多字节字符的字符分类,主要用于肯定字符串的字符边界以及编译程序使用何种语言发出诊断消息;默认设置与 LANG相同。中文相关的几项:"zh_CN.GB2312 , zh_CN.GB18030 , zh_CN.GBK , zh_CN.UTF-8 , zh_TW.BIG5"。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3