我从各种来源(虽然主要是从我的一个同事那里)听说,在g++中使用-O3优化级别编译是“危险的”,除非被证明是必要的,否则一般应该避免。

这是真的吗?如果是,为什么?我应该坚持用-O2吗?


当前回答

是的,O3更麻烦。我是一名编译器开发人员,在构建自己的软件时,我已经确定了由O3生成有bug的SIMD汇编指令引起的清晰而明显的gcc错误。据我所知,大多数产品软件都带有O2,这意味着O3在测试和错误修复方面得到的关注较少。

可以这样想:O3在O2上加了更多的变换,O2在O1上加了更多的变换。从统计学上讲,更多的转换意味着更多的bug。这对任何编译器都适用。

其他回答

在我的经验中,对整个程序应用-O3几乎总是使它变慢(相对于-O2),因为它打开了主动循环展开和内联,使程序不再适合指令缓存。对于较大的程序,-O2相对于-Os也是如此!

-O3的预期使用模式是,在对程序进行概要分析之后,手动将其应用于少量包含关键内部循环的文件,这些内部循环实际上受益于这些积极的空间换取速度的权衡。GCC的新版本有一个配置文件引导的优化模式,可以(IIUC)选择性地将-O3优化应用于热函数——有效地自动化这个过程。

是的,O3更麻烦。我是一名编译器开发人员,在构建自己的软件时,我已经确定了由O3生成有bug的SIMD汇编指令引起的清晰而明显的gcc错误。据我所知,大多数产品软件都带有O2,这意味着O3在测试和错误修复方面得到的关注较少。

可以这样想:O3在O2上加了更多的变换,O2在O1上加了更多的变换。从统计学上讲,更多的转换意味着更多的bug。这对任何编译器都适用。

在gcc(2.8等)的早期,在egcs和redhat 2.96的时代,-O3有时很有bug。但这是十多年前的事情了,-O3与其他级别的优化(在bug方面)并没有太大的不同。

然而,它确实倾向于揭示人们依赖于未定义的行为的情况,由于更严格地依赖于语言的规则,特别是边缘情况。

就我个人而言,我使用-O3在金融部门运行生产软件很多年了,还没有遇到过如果我使用-O2就不会出现的错误。

应大众要求,在此增加一项:

-O3 and especially additional flags like -funroll-loops (not enabled by -O3) can sometimes lead to more machine code being generated. Under certain circumstances (e.g. on a cpu with exceptionally small L1 instruction cache) this can cause a slowdown due to all the code of e.g. some inner loop now not fitting anymore into L1I. Generally gcc tries quite hard to not to generate so much code, but since it usually optimizes the generic case, this can happen. Options especially prone to this (like loop unrolling) are normally not included in -O3 and are marked accordingly in the manpage. As such it is generally a good idea to use -O3 for generating fast code, and only fall back to -O2 or -Os (which tries to optimize for code size) when appropriate (e.g. when a profiler indicates L1I misses).

如果你想把优化发挥到极致,你可以在gcc中通过——param来调整与某些优化相关的成本。另外请注意,gcc现在能够在函数中添加属性来控制这些函数的优化设置,因此当您发现在某个函数中使用-O3有问题时(或者想要为该函数尝试特殊的标志),您不需要使用O2编译整个文件甚至整个项目。

otoh似乎在使用-Ofast时必须小心,它表示:

-Ofast启用所有-O3优化。 它还可以实现并非对所有标准都有效的优化 兼容的程序。

这让我得出结论-O3是完全符合标准的。

-O3选项除了所有较低级别的' -O2 '和' -O1 '的优化之外,还会打开更昂贵的优化,例如函数内联。' -O3 '优化级别可以提高结果可执行文件的速度,但也可以增加其大小。下 在某些情况下,这些优化并不有利,这个选项实际上可能会使程序变慢。

最近我在使用g++进行优化时遇到了一个问题。该问题与PCI卡有关,其中寄存器(用于命令和数据)由内存地址表示。我的驱动程序将物理地址映射到应用程序中的一个指针,并将它给了被调用的进程,它是这样工作的:

unsigned int * pciMemory;
askDriverForMapping( & pciMemory );
...
pciMemory[ 0 ] = someCommandIdx;
pciMemory[ 0 ] = someCommandLength;
for ( int i = 0; i < sizeof( someCommand ); i++ )
    pciMemory[ 0 ] = someCommand[ i ];

卡片没有像预期的那样发挥作用。当我看到汇编代码时,我明白编译器只将someCommand[最后一个]写入pcmemory,省略了所有前面的写入。

总之:在优化时要做到准确和细心。