1. 介绍
什么是CFLAGS和CXXFLAGS
人们用环境变量CFLAGS和CXXFLAGS来告诉GNU编译器集合(GNU Compiler Collection),即gcc,在编译源代码时使用哪些选项。CFLAGS用于C代码,CXXFLAGS用于C++代码。
它们可以用来减少程序的调试信息数量,增加错误警告等级以及优化代码的生成。GNU gcc手册上维护着一个可用的选项及其作用的完整列表。
如何使用?
使用CFLAGS和CXXFLAGS有两种方法。第一,在每个程序由automake生成的Makefile中使用。
但是,安装Portage树中的软件包时并不能这么干,而要在/etc/make.conf中设置CFLAGS和CXXFLAGS。这样所有的软件包都会用你指定的选项来编译。
代码 1.1: /etc/make.conf中的CFLAGS
CFLAGS="-march=athlon64 -O2 -pipe"
CXXFLAGS="${CFLAGS}"
如你所见,用CFLAGS中的所有选项来设置CXXFLAGS。在多数情况下这就是你想要的。你可以不用为CXXFLAGS指定额外的选项了。
误解
指定CFLAGS和CXXFLAGS是使由源代码生成的二进制文件更小更快的有效手段。不过也可能产生副作用,如二进制文件体积膨胀,执行缓慢,甚至造成编译失败!
CFLAGS不是万灵药;它们不会自动使你的系统跑的更快或减少二进制文件占用的磁盘空间。大把地增加标记以优化(压榨)系统是绝对要失败的方案。过犹不及。
尽管网上有些人吹的很好,但实际上过激的CFLAGS和CXXFLAGS给你程序带来的坏处远多于好处。请记住那些标记一开始之所以出现是因为它们只适用于特定场合的特定目的。某一个特定的CFLAG对有些代码有用不代表它就适合用来编译你机器上安装的每一个软件。
准备好了?
现在你已经了解其中的一些风险了,让我们看一些对你计算机来说健壮、安全的优化。这对你下次向Bugzilla报告问题大有好处,你会因此受到开发者的欢迎。(开发者通常会要求你用最少的CFLAGS重新编译软件包以观察问题是否持续存在。记住,过激的标记会使代码崩溃。)
2. 优化
基础
使用CFLAGS和CXXFLAGS的目的是生成契合你系统的代码。如果可以的话,应该能在完美运行之外还能生成精简而快速的代码。但有时候鱼与熊掌不可兼得,所以我们会选择已知没有问题的选项组合。理想来说,它们在所有的CPU构架上都有提供。我们也会在稍后提到激进的标记以便让你知道该注意些什么。我们不会讨论到在gcc手册中提到的每一个选项(有上百个之多),但是基本的,常用的标记都会涵盖到。
注意: 当你不能确定一个标记的作用时,请查阅gcc手册的相关章节。如果还有疑问,试试Google,或者检索gcc邮件列表。
-march
第一个重要的选项是-march。这个选项告诉编译器该为你的处理器架构(architecture)(或arch)生成何种代码,它告诉编译器只为特定类型的CPU生成代码。不同的CPU具有不同的能力,支持不同的指令集,以及不同的执行代码方式。-march标记指示编译器根据你CPU的能力、特征、指令集、怪癖等生成特定的代码。
即使/etc/make.conf中的CHOST变量指定了所使用的通用构架,-march还是可以用来为特定的处理器优化程序。x86和x86-64(也包括其他的)的CPU尤其应该利用-march标记。
你用哪种类型的CPU?用以下命令检测:
代码 2.1: 检测CPU信息
$ cat /proc/cpuinfo
现在来看一个-march的实际的例子。这是旧的Pentium III芯片的例子:
代码 2.2: /etc/make.conf: Pentium III
CFLAGS="-march=pentium3"
CXXFLAGS="${CFLAGS}"
这个则是64位AMD CPU的例子:
代码 2.3: /etc/make.conf: AMD64
CFLAGS="-march=athlon64"
CXXFLAGS="${CFLAGS}"
-mtune和-mcpu标记也是可用的。这两个标记通常只在没有-march选项的时候才用到;特定的处理器可能要求使用-mtune甚至-mcpu。糟糕的是,gcc在不同构架上的表现并非完全一致。
对于x86和x86-64的CPU,-march将使用所指定CPU的全部可用指令集和正确的ABI来生成代码;并不会向后兼容其他旧的或者不同的CPU。如果你只需要在你当前运行Gentoo的机器上执行代码,那么就可以继续使用-march。只有在为i386和i486之类的旧CPU生成代码时,才需要考虑使用-mtune。使用-mtune生成代码比使用-march更通用;虽然它能为特定的CPU优化代码,但是并不会使用特有的指令集和ABI。别在x86或x86-64系统上使用-mcpu,因为在这两个构架上已经废弃了。
只有非x86/x86-64 CPU(如Sparc、Alpha和PowerPC)可能要求使用-mtune或-mcpu来代替-march。在这些构架上,-mtune/-mcpu的行为有时就像(x86/x86-64上的)-march,只是换了个名称。gcc在诸构架上的行为和标记的命名并不一致。所以请查阅gcc手册来确认在你的系统上该用哪个。
注意: 要获得更多-march/-mtune/-mcpu设置的建议,请阅读你的构架对应的Gentoo安装手册。还有就是gcc手册上的架构特有选项列表,其中更加详细地解释了-march、-mcpu和-mtune之间的区别。
-O
接下来是-O变量。这个选项控制所有的优化等级。使用优化选项会使编译过程耗费更多的时间,并且占用更多的内存,尤其是在提高优化等级的时候。
-O设置一共有五种:-O0、-O1、-O2、-O3和-Os。你只能在/etc/make.conf里面设置其中的一种。
除了-O0以外,每一个-O设置都会多启用几个选项,请查阅gcc手册的优化选项章节,以便了解每个-O等级启用了哪些选项及它们有何作用。
让我们来逐一考察各个优化等级:
•-O0:这个等级(字母“O”后面跟个零)关闭所有优化选项,也是CFLAGS或CXXFLAGS中没有设置-O等级时的默认等级。这样就不会优化代码,这通常不是我们想要的。
•-O1:这是最基本的优化等级。编译器会在不花费太多编译时间的同时试图生成更快更小的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成。
•-O2:-O1的进阶。这是推荐的优化等级,除非你有特殊的需求。-O2会比-O1启用多一些标记。设置了-O2后,编译器会试图提高代码性能而不会增大体积和大量占用的编译时间。
•-O3:这是最高最危险的优化等级。用这个选项会延长编译代码的时间,并且在使用gcc4.x的系统里不应全局启用。自从3.x版本以来gcc的行为已经有了极大地改变。在3.x,-O3生成的代码也只是比-O2快一点点而已,而gcc4.x中还未必更快。用-O3来编译所有的软件包将产生更大体积更耗内存的二进制文件,大大增加编译失败的机会或不可预知的程序行为(包括错误)。这样做将得不偿失,记住过犹不及。在gcc 4.x.中使用-O3是不推荐的。
•-Os:这个等级用来优化代码尺寸。其中启用了-O2中不会增加磁盘空间占用的代码生成选项。这对于磁盘空间极其紧张或者CPU缓存较小的机器非常有用。但也可能产生些许问题,因此软件树中的大部分ebuild都过滤掉这个等级的优化。使用-Os是不推荐的。
正如前面所提到的,-O2是推荐的优化等级。如果编译软件出现错误,请先检查是否启用了-O3。再试试把CFLAGS和CXXFLAGS倒回到较低的等级,如-O1甚或-O0 -g2 -ggdb(用来报告错误和检查可能存在的问题),再重新编译。
-pipe
-pipe是个安全而有趣的标记。它对代码生成毫无影响,但是可以加快编译过程。此标记指示编译器在不同编译时期使用pipe而不是临时文件。
-fomit-frame-pointer
这是个用来削减代码尺寸的常用标记。在所有不影响除错(例如x86-64)的构架上,所有的-O等级(除了-O0)中都启用了它,但是也可能需要手动添加到你的标记中。尽管GNU gcc手册没有明确指出-O会启用这个标记的所有构架,你需要在x86上手动启用它。使用这个标记会使除错难以进行。
特别的,它会使排查Java程序的故障变得困难,尽管Java代码并不是唯一受此选项影响的代码。所以此标记虽然有用,但也会使除错变得困难,特别是backtrace将变得毫无用处。然而,你不准备做软件除错并且没有在CFLAGS中加入-ggdb之类与除错相关的标记的话,那就可以试试-fomit-frame-pointer。
重要: 请勿联合使用-fomit-frame-pointer和与之相似的-momit-leaf-frame-pointer。开启后者并无多大用处,因为-fomit-frame-pointer已经把事情搞定了。此外,-momit-leaf-frame-pointer还将降低代码性能。
-msse, -msse2, -msse3, -mmmx, -m3dnow
这些标记启用了x86和x86-64构架的SSE、SSE2、SSE3、MMX和3DNow!指令集。他们主要用于多媒体,游戏,及其他浮点运算密集的任务,虽然也包括了一些其他的数学增强指令。比较新的CPU都具有这些指令。
重要: 运行cat /proc/cpuinfo以确认你的CPU是否支持这些指令集。输出会包括所有支持的指令集。注意pni是SSE3的别名。
只要正确设置了-march就不需要添加额外的标记(例如,-march=nocona暗含了-msse3)。要注意的是一些新的VIA和AMD64 CPU包含了-march并不隐含的指令集(例如SSE3)。对于这些CPU就需要在检查cat /proc/cpuinfo输出之后手动加入适当的标记。
注意: 请查阅x86和x86-64特定标记列表以便确认适当的CPU类型标记启用了哪些指令集。如果某指令集已经列出了,就会由对应的-march设定开启,不再需要手动加入了。
3. 优化的常见问题
我通过-funroll-loops -fomg-optimize获得了更好的性能!
并非如此,这样认为只是因为你深信标记越多越好而已。全局的使用激进标记对应用软件无益。即使gcc手册也提到使用-funroll-loops和-funroll-all-loops会使代码膨胀运行减慢。但是由于某些原因,这两个标记以及-ffast-math、-fforce-mem、-fforce-addr和类似的标记,还在那些想获得最多吹牛资本的玩家中流行着。
事实上那些都是危险的过激标志。好好逛逛Gentoo论坛和Bugzilla看看这些标记有什么好的:一点也不好!
你无需在全局范围的CFLAGS或CXXFLAGS中使用那些标记。他们只会影响性能,就好似迫使一个高性能系统运行在崩溃的边缘。这些标记除了使代码膨胀和得到非法或无法解决bug之外并无他用。
你不需要这些危险的标记。切勿使用。谨遵基本的:-march,-O和-pipe。
比3还高的-O等级又怎样?
有些用户吹嘘说使用-O4,-O9等可以获得更高的性能,但事实上比3更高的-O是无效的。就算编译器可以接受像-O4之类的CFLAGS,编译时也会忽略掉。最高只对-O3进行优化。
需要更多证据?看看gcc源代码:
代码 3.1: -O源代码
if (optimize >= 3)
{
flag_inline_functions = 1;
flag_unswitch_loops = 1;
flag_gcse_after_reload = 1;
/* Allow even more virtual operators. */
set_param_value ("max-aliased-vops", 1000);
set_param_value ("avg-aliased-vops", 3);
}
如你所见,任何大于3的值都只被当成-O3。
什么是冗余标记?
有些人在有些选项已经被-O等级启用的情况下仍多余地把它们加入/etc/make.conf中的CFLAGS和CXXFLAGS。有时候人们这样做是出于无知,但有时候这样做是为了防止标记被过滤或替换。
在Portage树中不少ebuild都会过滤或替换标记。这么做通常是因为某些软件包在特定的-O等级下无法编译,或者源代码对附加的标记过于敏感。ebuild可能过滤部分或全部CFLAGS和CXXFLAGS,或者将-O替换为其他等级。
Gentoo开发者手册概述了在何处过滤替换及它如何起作用。
为一个特定的等级追加该等级已经启用的标记可以规避-O过滤,比如-O3,可以这样做:
代码 3.2: 指定额外的CFLAGS
CFLAGS="-O3 -finline-functions -funswitch-loops"
然而,这样做可不好。过滤CFLAGS必有其原因。如果标记被过滤,那就意味着用那些标记来编译某个软件包是不安全的。毫无疑问,用-O3编译整个系统是不安全的,如果开启了这个等级中的某些标记,对于特定软件包将产生问题。别以为你比那些包的维护者更聪明。请相信开发者。过滤和替换标记是为了你好!如果ebuild指定了其他的标记,就别再动它了。
如果你用不被接受的标记来构建软件包,你很可能会不断地遇到问题。当你到Bugzilla报告问题时,你在/etc/make.conf中使用的标记将会被其他人看到,你将会被告知要去掉那些标记再重新编译。要想避免重新编译的麻烦,那就请从一开始就不要使用多余的标记。别以为你比开发者更懂。
什么是LDFLAGS?
在基本的profile里Gentoo开发者已经设置了基本的,安全的LDFLAGS,所以没必要去改动。
可以为单个包分别指定标记吗?
为单个包指定CFLAGS或者其他变量是不支持的。不过这里有一个有滥用之嫌的手段可以强制Portage这么做。
你不应强制Portage使用单个包的指定标记,因为这不受任何支持还很可能产生错综复杂的问题。只要在/etc/make.conf设置系统范围的标记就可以了。
4. 资源
以下资源对优化的深入理解有所帮助:
•GNU gcc手册
•Gentoo安装手册的第五章
•man make.conf
•Wikipedia
•Acovea,是个用来评定不同的编译器标记对于生成代码影响的测评套件,虽然它给出的代码建议不宜在系统范围使用。可以在Portage里通过emerge acovea安装。
•Gentoo论坛