C++ 我什么时候可以自信地用-O3编译程序?

C++ 我什么时候可以自信地用-O3编译程序?,c++,gcc,compiler-construction,compilation,C++,Gcc,Compiler Construction,Compilation,我见过很多人抱怨-O3选项: 我查看GCC中的手册: 我还确认了代码,以确保两个选项是-O3上包含的唯一两个优化: if (optimize >= 3){ flag_inline_functions = 1; flag_rename_registers = 1; } 对于这两种优化: -finline函数在某些情况下很有用(主要是在C++中),因为它允许我们使用-finline限制定义内联函数的大小(默认为600)。当设置较高的内联限制时,编译器可能会报告一个错

我见过很多人抱怨
-O3
选项:

我查看GCC中的手册:

我还确认了代码,以确保两个选项是
-O3
上包含的唯一两个优化:

if (optimize >= 3){
    flag_inline_functions = 1;
    flag_rename_registers = 1;
}
对于这两种优化:

  • -finline函数
    在某些情况下很有用(主要是在C++中),因为它允许我们使用-finline限制定义内联函数的大小(默认为600)。当设置较高的内联限制时,编译器可能会报告一个错误,抱怨内存不足
  • -frename寄存器
    通过使用寄存器分配后剩余的寄存器,尝试避免计划代码中的错误依赖。这种优化将最有利于具有大量寄存器的处理器
对于内联函数,虽然它可以减少函数调用的数量,但它可能会导致大量二进制文件,因此
-finline函数
可能会引入严重的缓存惩罚,甚至比-O2更慢。我认为缓存惩罚不仅仅取决于程序本身

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

我的问题有2.5个部分:

  • 使用-O3选项是否可以让程序运行得更快取决于底层平台/体系结构,这一说法正确吗?[答]

    编辑:

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

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

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

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

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

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

    因此,简而言之,唯一有效的答案是:两者都尝试一下,并对代码进行基准测试,看看哪个更快

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

  • 我看到一些程序在用-O3编译时崩溃了,这是确定性的吗
  • 如果程序是单线程的,则程序使用的所有算法都是确定性的,如果从一个运行到另一个运行的输入是相同的,则是。如果其中任何一个条件都不成立,答案是“不一定”

    如果编译时不使用-O3,则同样适用

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

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


    在浮点寄存器的精度高于double的机器上,浮点操作可能会导致奇怪的行为。比如说,

    void add (double a, double b, double & result) {
       double temp = a + b;
       result = temp;
       if (result != temp) {
          throw FunkyAdditionError (temp);
       }
    }
    
    编译一个使用未优化的
    add
    函数的程序,您可能永远不会看到任何
    FunkyAdditionError
    异常。编译优化后,某些输入会突然启动,导致这些异常。问题是,在进行优化时,编译器将
    temp
    作为一个寄存器,而
    result
    作为一个引用,不会编译到寄存器中。添加一个
    内联
    限定符,当使用
    -O3
    编译编译器时,这些异常可能会消失,因为现在
    结果
    也可以是寄存器。关于浮点运算的优化可能是一个棘手的问题

    最后,让我们看看其中一个例子,当一个程序用-O3编译时,事情确实在夜间发生了碰撞。问题只出现在-O3中,因为编译器可能内联了
    distance
    函数,但在扩展精度浮点寄存器中保留了一个(但不是两个)结果。通过这种优化,c
    void add (double a, double b, double & result) {
       double temp = a + b;
       result = temp;
       if (result != temp) {
          throw FunkyAdditionError (temp);
       }
    }