优化级别-O3在g ++中是危险的吗?

Dun*_*nie 217 c++ optimization g++ compiler-flags

我从各种来源(虽然大多数来自我的同事)中听到过,-O3用g ++ 的优化级别进行编译在某种程度上是"危险的",并且除非被证明是必要的,否则应该避免.

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

Pla*_*aHH 209

在GCC(2.8等)的早期阶段,在电梯群控系统的时候,和redhat 2.96 -O3是相当马车的时候.但这是十多年前的事了,而-O3与其他优化水平(在马车上)并没有多大差别.

然而,它确实倾向于揭示人们依赖未定义行为的情况,因为更严格地依赖于语言的规则,特别是角落案例.

作为我个人而言,我现在正在运行的生产软件在金融领域具有多年-O3,还没有遇到过,不会一直存在,如果我会用-02的错误.

根据大众需求,这里增加了一个:

-O3,尤其是-funroll-loops(未由-O3启用)等附加标志有时会导致生成更多的机器代码.在某些情况下(例如,在具有异常小的L1指令高速缓存的cpu上),由于例如一些内部循环的所有代码现在不再适合L1I,这可能导致减速.通常gcc很难不生成如此多的代码,但由于它通常会优化通用情况,因此可能会发生这种情况.特别容易出现这种情况的选项(如循环展开)通常不包含在-O3中,并在联机帮助页中相应标记.因此,通常最好使用-O3来生成快速代码,并且在适当时(例如,当分析器指示L1I未命中时)仅回退到-O2或-Os(其尝试优化代码大小).

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

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

-Ofast启用所有-O3优化.它还支持对所有符合标准的程序无效的优化.

这让我得出结论,-O3旨在完全符合标准.

  • @DarioOO我觉得恳求"代码臃肿"是一个很受欢迎的事情,但我几乎从来没有看到它支持基准测试.它在很大程度上取决于架构,但每当我看到已发布的基准测试时(例如http://www.phoronix.com/scan.php?page=article&item=gcc_47_optimizations&num=1),它表明O3在绝大多数情况下都更快.我已经看到了证明代码膨胀实际上是一个问题所需的分析和仔细分析,并且它通常只发生在以极端方式包含模板的人身上. (4认同)
  • 我这样做是为了速度.O3大多数时候会让事情变慢.不知道确切原因,我怀疑它会污染指令缓存. (3认同)
  • 我只是使用相反的东西.我总是使用-Os或-O2(有时候O2生成一个较小的可执行文件)..在分析之后,我在代码的一部分上使用O3,这需要更多的执行时间,而单独这样可以使速度提高20%. (2认同)

zwo*_*wol 40

这已经在Neel的回答中说过,但不是明显或强烈的:

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

预期的使用模式-O3是,在对程序进行概要分析后,您手动将其应用于少数几个包含关键内部循环的文件,这些文件实际上受益于这些激进的空间 - 速度权衡.对于最近的GCC,我认为闪亮的新链接时间配置文件引导优化模式可以选择性地将-O3优化应用于热门功能 - 有效地自动化此过程.

  • "几乎总是"?使它"50-50",我们将达成协议;-). (8认同)
  • gcc `-O3` 已经很长时间没有包含 `-funroll-loops` 了,因为您指出了展开循环而不是非常热门的循环的问题。“-O3”仍然包含自动矢量化,但 GCC12 将其添加到“-O2”中。(在更改后的几个版本中,处理对齐的循环序言通常会变得相当臃肿,尽管可能不是向量宽度倍数的行程计数仍然可能非常臃肿,尤其是带有 AVX 的“uint8_t”循环512。) (4认同)

nee*_*eel 9

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

  • 我知道一些"明显的优化"可能会使程序变慢,但是你有一个声称GCC -O3使程序变慢的源吗? (3认同)
  • @MooingDuck:虽然我无法引用来源,但我记得在一些较旧的 AMD 处理器上遇到过这样的情况,这些处理器具有相当小的 L1I 缓存(~10k 指令)。我确信谷歌有更多的内容供感兴趣的人使用,但特别是像循环展开这样的选项不是 O3 的一部分,而且这些选项会增加很多大小。-Os 适合当您想要使可执行文件最小化时。甚至 -O2 也会增加代码大小。gcc 资源管理器是一个很好的工具,可以处理不同优化级别的结果。 (2认同)
  • @PlasmaHH:实际上,编译器可能会搞砸很小的缓存大小,这是个好点。这真是一个很好的例子。请把它写在答案中。 (2认同)
  • @PlasmaHH Pentium III 有 16KB 代码缓存。AMD 的 K6 及以上实际上有 32KB 指令缓存。P4 开始时价值约为 96KB。Core I7 实际上有一个 32KB 的 L1 代码缓存。指令解码器现在很强大,所以你的 L3 足够好,几乎可以在任何循环中使用。 (2认同)

Dav*_*ger 7

是的,O3令人讨厌。我是一名编译器开发人员,而且我已经确定由O3引起的清晰明显的gcc错误,这些错误是由O3在构建自己的软件时生成有问题的SIMD汇编指令引起的。据我所知,大多数生产软件都附带O2,这意味着O3在测试和错误修复方面的关注度会降低。

这样想:O3在O2之上添加了更多的转换,在O1之上添加了更多的转换。从统计上讲,更多的转换意味着更多的错误。对于任何编译器都是如此。

  • 这太简单了。当 -O3 完全删除无法访问的代码时,-O2 或 -O1 在无法访问的代码中所做的任何优化都变得无关紧要。这表明优化不会像此处假设的那样进行堆栈。另外,“当构建我自己的软件时”是一个明确的警告信号。-O3 将根据正在编译的代码的行为应用优化。代码中任何未定义的行为都可能触发意外的优化。但未定义的行为意味着编译器**不可能是错误的**。 (5认同)