【问题标题】:Why isn't this C++ member function optimized by the compiler with -O3?为什么编译器没有用-O3优化这个C++成员函数?
【发布时间】:2017-07-25 22:24:00
【问题描述】:

下面声明的 C++ vector 类中的 norm 成员函数被标记为 const 并且(据我所知)不包含任何副作用。

template <unsigned int N>
struct vector {
  double v[N];

  double norm() const {
    double ret = 0;
    for (int i=0; i<N; ++i) {
      ret += v[i]*v[i];
    }
    return ret;
  }
};

double test(const vector<100>& x) {
  return x.norm() + x.norm();
}

如果我在 gcc 编译器(5.4 版)和优化打开(即-O3)的情况下多次调用const 实例化vector(参见上面的test 函数)上的norm,那么编译器内联norm,但仍然多次计算norm 的结果,即使结果不应该改变。为什么编译器不优化对norm 的第二次调用并且只计算一次这个结果? This answer 似乎表明如果编译器确定 norm 函数没有任何副作用,则编译器应该执行此优化。为什么在这种情况下不会发生这种情况?

请注意,我正在确定编译器使用 Compiler Explorer 生成的内容,并且 gcc 5.4 版的程序集输出如下所示。 clang 编译器给出了类似的结果。另请注意,如果我使用 gcc 的编译器属性手动将 norm 标记为使用 __attribute__((const)) 的 const 函数,那么第二个调用会按照我的意愿进行优化,但我的问题是为什么 gcc(和 clang)不这样做自动因为norm 定义可用?

test(vector<100u>&):
        pxor    xmm2, xmm2
        lea     rdx, [rdi+800]
        mov     rax, rdi
.L2:
        movsd   xmm1, QWORD PTR [rax]
        add     rax, 8
        cmp     rdx, rax
        mulsd   xmm1, xmm1
        addsd   xmm2, xmm1
        jne     .L2
        pxor    xmm0, xmm0
.L3:
        movsd   xmm1, QWORD PTR [rdi]
        add     rdi, 8
        cmp     rdx, rdi
        mulsd   xmm1, xmm1
        addsd   xmm0, xmm1
        jne     .L3
        addsd   xmm0, xmm2
        ret

【问题讨论】:

  • (const好像错了,我觉得pure更好,但是优化没有发生)这是优化顺序的问题。如果函数是 const,x+x 很早就优化为 2*x(x 的单次出现)。否则,如果您将该函数标记为 noinline,则第二次调用将被优化为冗余。但是如果首先发生内联,那么编译器就很难注意到两个循环都在计算相同的东西(循环融合看起来是朝那个方向迈出的潜在有用的一步)。
  • 按值传递向量时会发生同样的事情吗?仅仅因为你有一个 const 引用并不意味着它没有在其他地方被修改。 const 引用只是一个信号,表明被调用者(而不是调用者)不会更改被引用的对象。国际海事组织。在一般情况下,GCC 优化第二次调用是错误的,因为您添加了 __attribute__((const)) 但它可能能够进行更复杂的分析并确定在有限的测试用例中可以省略第二次调用.
  • 我猜想在不正确的互斥环境中,另一个函数可能会在调用 norm 之间更改数组中的内容。
  • 对我来说更有趣的是编译器正在使用 SSE 进行优化,但是一次乘以 1 (mulsd) 而不是一次乘以 2 (mulpd)。更多证据表明,如果您有时间要求严格的代码,请不要依赖编译器来做“正确的事情”。
  • @J.H.Bonarius:那将是未定义的行为。

标签: c++ gcc optimization clang++


【解决方案1】:

编译器可以计算norm的结果并多次重复使用。例如。 with the -Os switch:

test(vector<100u> const&):
        xorps   xmm0, xmm0
        xor     eax, eax
.L2:
        movsd   xmm1, QWORD PTR [rdi+rax]
        add     rax, 8
        cmp     rax, 800
        mulsd   xmm1, xmm1
        addsd   xmm0, xmm1
        jne     .L2
        addsd   xmm0, xmm0
        ret

缺少优化不是由于not-associative-floating-point-mathsome observable-behavior-issue


在不正确的互斥环境中,另一个函数可能会在调用 norm 之间更改数组中的内容

这可能会发生,但编译器不会担心(例如https://stackoverflow.com/a/25472679/3235496)。


使用-O2 -fdump-tree-all 开关编译示例,您可以看到:

  • g++ 正确地将vector&lt;N&gt;::norm() 检测为纯函数(输出文件.local-pure-const1);
  • 内联发生在早期阶段(输出文件.einline)。

另请注意,将norm 标记为__attribute__ ((noinline)) compiler performs CSE

test(vector<100u> const&):
    sub     rsp, 8
    call    vector<100u>::norm() const
    add     rsp, 8
    addsd   xmm0, xmm0
    ret

Marc Glisse(可能)是对的。

un-inline the recurrent expression 需要更高级的通用子表达式消除

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-13
    • 1970-01-01
    • 2015-06-06
    • 1970-01-01
    • 2016-06-22
    • 1970-01-01
    • 1970-01-01
    • 2020-01-01
    相关资源
    最近更新 更多