我什么时候可以放心地用-O3编译程序

When can I confidently compile program with -O3?

本文关键字:-O3 编译程序 心地 什么时候      更新时间:2023-10-16

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

  • GCC:程序无法使用编译选项-O3
  • David Hammen提供的浮点问题

我查看了GCC的手册:

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

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

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

对于这两个优化:

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

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

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

我的问题有2.5部分:

  1. 我说一个程序是否可以使用-O3选项更快地运行取决于底层平台/架构,这是对的吗?[回答]

    编辑:

    第一部分已被确认为真实。David Hammen还声称,我们应该非常小心优化和浮点运算在英特尔和AMD等具有扩展精度浮点寄存器的机器上的交互。

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

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

  3. 如果第一个问题的答案是"否";是";,那么当我改变目标平台或在具有不同机器的分布式系统中时,我可能需要在-O3-O2之间改变。有没有什么通用的方法来决定我是否可以使用-O3来提高性能?例如,更多寄存器、短内联函数等。[Answered]

    编辑:第三部分由Louen回答为";平台的多样性使得关于这个问题的一般推理是不可能的;在评估-O3的性能增益时,我们必须同时尝试这两种方法,并对我们的代码进行基准测试,看看哪种更快。

  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编译时,这些异常可能会消失,因为现在result也可以是寄存器。浮点运算的优化可能是一个棘手的问题。

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

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

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

因此,简而言之,唯一有效的答案是:同时尝试这两种方法,并对代码进行基准测试,看看哪种更快。

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