4

我看到很多人抱怨-O3选项:

我查看了 GCC 的手册:

   -O3    Optimize yet more.  -O3 turns on all optimizations
          specified   by   -O2   and   also   turns  on  the
          -finline-functions and -frename-registers options.

而且我还确认了代码以确保两个选项是-O3on 中仅有的两个优化:

if (optimize >= 3){
    flag_inline_functions = 1;
    flag_rename_registers = 1;
}

对于这两个优化:

  • -finline-functions在某些情况下(主要用于 C++)很有用,因为它允许我们使用 -finline-limit 定义内联函数的大小(默认为 600)。当设置高内联限制时,编译器可能会报告一个错误,抱怨内存不足。
  • -frename-registers尝试通过使用寄存器分配后剩余的寄存器来避免调度代码中的错误依赖性。这种优化将最有利于具有大量寄存器的处理器。

对于内联函数,虽然可以减少函数调用的次数,但可能会导致二进制文件很大,因此-finline-functions可能会引入严重的缓存惩罚,甚至比 -O2 还要慢。我认为缓存惩罚不仅取决于程序本身。

对于重命名寄存器,我认为它不会对像 x86 这样的 cisc 架构产生任何积极影响。

我的问题有 2.5 个部分:

  1. 我是否可以声称使用 -O3 选项是否可以更快地运行程序取决于底层平台/架构?[已回答]

    编辑:

    第一部分已被证实为真实。David Hammen 还声称,我们应该非常小心优化和浮点操作如何在具有扩展精度浮点寄存器(如 Intel 和 AMD)的机器上进行交互。

  2. 我什么时候可以自信地使用-O3选项?我想这两个优化,尤其是重命名寄存器可能会导致与 -O0/O2 不同的行为。我看到一些编译的程序-O3在执行过程中崩溃了,它是确定性的吗?如果我运行一个可执行文件一次而没有任何崩溃,这是否意味着它可以安全使用-O3

    编辑:确定性与优化无关,它是一个多线程问题。-O3但是,对于多线程程序,当我们运行一次可执行文件而没有错误时,使用它是不安全的。David Hammen 表明-O3浮点运算的优化可能违反严格的弱排序标准进行比较。当我们想使用选项时,还有其他需要注意的问题吗?-O3

  3. 如果第一个问题的答案是“是”,那么当我更改目标平台或在具有不同机器的分布式系统中时,我可能需要在 和 之间进行-O3更改-O2。有没有什么通用的方法来决定我是否可以通过 获得性能改进-O3?比如更多的寄存器,短的内联函数等【已回答】

    编辑:Louen 回答了第 3 部分,因为“平台的多样性使得对这个问题的一般推理变得不可能” 当通过 评估性能增益时-O3,我们必须对两者进行尝试并对我们的代码进行基准测试以查看哪个更快。

4

2 回答 2

7
  1. 我看到一些程序在使用 -O3 编译时崩溃了,这是确定性的吗?

如果程序是单线程的,程序使用的所有算法都是确定性的,如果从运行到运行的输入是相同的,是的。如果其中任何一个条件不成立,答案是“不一定”。

如果您在不使用 -O3 的情况下进行编译,这同样适用。

如果我运行一次可执行文件而没有任何崩溃,是否意味着使用 -O3 是安全的?

当然不是。同样,如果您在不使用 -O3 的情况下进行编译,这同样适用。仅仅因为您的应用程序运行一次并不意味着它将在所有情况下都成功运行。这就是使测试成为难题的部分原因。


在浮点寄存器的精度高于双精度的机器上,浮点运算可能会导致奇怪的行为。例如,

void add (double a, double b, double & result) {
   double temp = a + b;
   result = temp;
   if (result != temp) {
      throw FunkyAdditionError (temp);
   }
}

编译一个未优化使用此函数的程序,add您可能永远不会看到任何FunkyAdditionError异常。编译优化并且某些输入会突然开始导致这些异常。问题在于,通过优化,编译器将temp生成一个寄存器result,而作为引用,不会被编译到寄存器中。添加一个inline限定符,当你的编译器编译时这些异常可能会消失,-O3因为 nowresult也可以是一个寄存器。关于浮点运算的优化可能是一个棘手的问题。

最后,让我们看一下使用 -O3, GCC 编译程序时确实出现问题的一种情况:程序不适用于编译选项 -O3。该问题仅发生在 -O3 中,因为编译器可能内联了该distance函数,但将其中一个(但不是两个)结果保存在扩展精度浮点寄存器中。通过这种优化,某些点p1p2可以导致两者p1<p2p2<p1评估为true。这违反了比较函数的严格弱排序标准。

您需要非常小心优化和浮点操作如何在具有扩展精度浮点寄存器的机器(例如,Intel 和 AMD)上交互。

于 2013-02-13T12:29:53.260 回答
4

1)和3)你是对的。有些程序可以从 -O3 启用的优化中受益,有些则不会。例如,内联更多的函数通常会更好(因为它绕过了函数调用机制的开销),但有时它会使事情变得更慢(例如通过削弱缓存局部性)。这和平台的多样性使得对这个问题的一般推理是不可能的。

因此,为了简短起见,唯一有效的答案是:两者都试一试,并对代码进行基准测试,看看哪个更快。

2)假设您没有遇到任何编译器/优化器错误(它们很少见,但它们存在),那么可以合理地假设您的程序中的错误仅在 -O3 处显示,那么它可能一直在那里,只有 -O3 选项发现了它。

于 2013-02-13T10:49:09.117 回答