我从多个渠道听说(尤其是我的一位同事),在g++中使用优化级别-O3
进行编译某种程度上是“危险的”,通常情况下应该避免,除非有必要。
这是真的吗?如果是,为什么?我应该坚持使用-O2
吗?
我从多个渠道听说(尤其是我的一位同事),在g++中使用优化级别-O3
进行编译某种程度上是“危险的”,通常情况下应该避免,除非有必要。
这是真的吗?如果是,为什么?我应该坚持使用-O2
吗?
然而,它往往会暴露人们依赖于未定义行为的情况,因为它更严格地依赖于语言规则,特别是边角情况。
作为个人的注释,我使用-O3运行金融行业的生产软件已经多年了,还没有遇到过如果我使用-O2就不会出现的错误。
根据广大需求,以下是补充内容:
-O3,特别是像-funroll-loops(不被-O3启用)这样的附加标志,有时会导致生成更多的机器代码。在某些情况下(例如在具有异常小的L1指令缓存的CPU上),这可能会因为所有内部循环的代码都不能再适应于L1I,而引起减速。通常,gcc尽力不生成太多的代码,但由于通常优化通用情况,所以这种情况可能会发生。特别容易出现此问题的选项(如循环展开)通常不包括在-O3中,并在manpage中标记。因此,通常最好在需要生成快速代码时使用-O3,并只在适当的情况下(例如在分析器指示L1I未命中时)退回到-O2或-Os(试图优化代码大小)。
如果你想将优化推向极致,你可以通过--param在gcc中进行微调,调整某些优化所涉及的成本。此外,请注意,现在gcc具有能够在函数中放置属性以控制针对这些函数的优化设置的功能,因此当你发现在一个函数中使用-O3存在问题(或者希望尝试仅针对该函数使用特殊标志)时,你不需要使用O2编译整个文件甚至整个项目。
另一方面,在使用-Ofast时需要小心,因为它声明:
-Ofast启用所有-O3优化。 它还启用了并非所有标准都有效的优化
这让我得出结论,-O3的目的是要完全符合标准的程序。
在我的经验中,对整个程序应用-O3
选项几乎总是会使程序变慢(相对于-O2
),因为它开启了激进的循环展开和内联优化,导致程序不再适合放入指令高速缓存中。对于较大的程序,-O2
相对于 -Os
也可能会出现这种情况!
使用-O3
的预期模式是,在对程序进行分析之后,将其手动应用于包含关键内部循环的少数文件,这些循环实际上受益于这些激进的以空间换时间的折衷方式。较新版本的 GCC 具有基于分析的优化模式,可以(如果我理解正确)选择性地将-O3
优化应用于热函数——有效地自动化此过程。
-O3
已经很长时间没有包含 -funroll-loops
了,因为您指出的展开非常热的循环以外的循环的问题。 -O3
仍然包括自动向量化,但 GCC12 将其添加到了 -O2
中。(在处理对齐的循环序言时,几个版本之后的更改通常会产生相当多的膨胀,尽管可能不是向量宽度的倍数的行程计数仍然可能非常膨胀,特别是带有 AVX-512 的 uint8_t
循环。) - Peter Cordes是的,O3存在更多的漏洞。我是一名编译器开发人员,并且在构建自己的软件时发现了明显的gcc漏洞,这些漏洞是由于O3生成有缺陷的SIMD汇编指令引起的。从我看到的情况来看,大多数生产软件都使用O2,这意味着关于测试和漏洞修复方面将会更少关注O3。
这样想吧:O3在O2之上添加更多的转换,而O2在O1之上添加更多的转换。从统计学角度来看,更多的转换意味着更多的漏洞。对于任何编译器都是如此。
-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 ];
这张卡的表现不如预期。当我查看汇编代码时,我发现编译器只在 pciMemory
写入了 someCommand[最后一个]
,忽略了所有之前的写入。
总之:优化时要准确、细心。
pciMemory
声明为 volatile
。 - Konrad Rudolph
-O3
被认为特别有缺陷吗?我想也许它会基于某些假设做出奇怪和美妙的事情,从而使未定义的行为变得更加糟糕,但那应该是你自己的责任。因此,通常来说这是没问题的。 - BoBTFish-O2
打开了-fstrict-aliasing
,如果您的代码能够通过此选项,则它可能会通过其他优化选项,因为这是一个人们一遍又一遍做错的选项。尽管如此,-fpredictive-commoning
只在-O3
中存在,启用它可能会导致代码中的并发不正确而引入错误。您的代码越正确,优化的风险就越小;-) - Steve Jessop