为什么GCC不能优化`std :: sqrt`?

uh *_*per 12 c++ optimization gcc

我有简单的程序:

#include <cmath>

int main()
{
    for (int i = 0; i < 50; ++i)
        std::sqrt(i);
}
Run Code Online (Sandbox Code Playgroud)

Clang 3.8优化了它-O3,但gcc 6.1没有.它产生以下组件:

## Annotations in comments added after the question was answered,
## for the benefit of future readers.
main:
    pushq   %rbx
    xorl    %ebx, %ebx
    jmp     .L2
.L4:
    pxor    %xmm0, %xmm0         # break cvtsi2sd's false dep on the old value of xmm0
    pxor    %xmm1, %xmm1         # xmm1 = 0.0
    cvtsi2sd        %ebx, %xmm0  # xmm0 = (double)i
    ucomisd %xmm0, %xmm1         # scalar double comparison, setting flags
    ja      .L7                  # if (0.0 > (double)i) sqrt(i);   // The `a` = above.  AT&T syntax reverses the order, but it's jump if  xmm1 above xmm0
.L2:
    addl    $1, %ebx             # i++
    cmpl    $50, %ebx
    jne     .L4                  # i != 50
    xorl    %eax, %eax
    popq    %rbx
    ret                          # return 0
.L7:
    call    sqrt                 # only executed on i < 0.  Otherwise gcc knows std::sqrt has no side effects.
    jmp     .L2
Run Code Online (Sandbox Code Playgroud)

如果我正确理解as-if规则,则允许编译器优化不会改变程序的可观察行为的代码,包括I/O写入等.我丢弃结果std::sqrt但不执行任何操作I/O.而且,#pragma STDC FENV_ACCESS我的计划中没有.是否std::sqrt有可观察到的副作用,或者是否有其他原因导致GCC没有优化呼叫?


(这个问题的初始版本有一个上限10e50,使其成为一个无限循环.同样的事情发生了50,所以nvm关于那个的评论.)

man*_*lio 12

这与循环展开有些相关.

int main()
{
  for (int i = 0; i <= 16; ++i)  // CHANGED NUMBER OF ITERATIONS
    std::sqrt(i);
}
Run Code Online (Sandbox Code Playgroud)

被替换为return 0;(g++ -O3 -fdump-tree-all).

如果您看一下,.115t.cunroll您可以看到代码最初转换为以下内容:

// ...

<bb 6>:
i_30 = i_22 + 1;
_32 = (double) i_30;
if (_32 < 0.0)
  goto <bb 7>;
else
  goto <bb 8>;

<bb 7>:
__builtin_sqrt (_32);

<bb 8>:
i_38 = i_30 + 1;
_40 = (double) i_38;
if (_40 < 0.0)
  goto <bb 9>;
else
  goto <bb 10>;

<bb 9>:
__builtin_sqrt (_40);

// ...
Run Code Online (Sandbox Code Playgroud)

并且编译器可以用实际数字"证明"每次调用sqrt都没有副作用(.125t.vrp2):

// ...

<bb 6>:
i_30 = 3;
_32 = 3.0e+0;
if (_32 < 0.0)
  goto <bb 7>;
else
  goto <bb 8>;

<bb 7>:
__builtin_sqrt (_32);

<bb 8>:
i_38 = 4;
_40 = 4.0e+0;
if (_40 < 0.0)
  goto <bb 9>;
else
  goto <bb 10>;

<bb 9>:
__builtin_sqrt (_40);

// ...
Run Code Online (Sandbox Code Playgroud)

如果迭代次数很多,gcc:

  • 不执行循环展开(除非强制使用类似的东西--param max-completely-peeled-insns=x --param max-completely-peel-times=y)
  • 是不是"足够聪明"来确定呼叫sqrt(i)没有副作用(但是一个小的帮助就足够了,例如std::sqrt(std::abs(i))).

gcc(v6.x)也不支持 #pragma STDC FENV_ACCESS 所以必须假设该pragma为ON(否则生成的代码可能不正确)(情况更复杂,参见bug 34678Tavian Barnes的评论).