为什么GCC不能优化`std :: sqrt`?
我有简单的程序:
#include <cmath>
int main()
{
for (int i = 0; i < 50; ++i)
std::sqrt(i);
}
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
如果我正确理解了as-if规则,那么编译器可以优化不会改变程序可观察行为(包括I / O写入等)的代码。我放弃std::sqrt
的结果并且不要不做任何I / O。 此外,我的程序中没有#pragma STDC FENV_ACCESS
。 std::sqrt
是否有可观察到的副作用或者GCC没有优化出呼叫的另一个原因?
(这个问题的最初版本有一个10e50
的上限,使其成为一个无限循环,同样的事情发生在50
,所以nvm关于这个问题的评论。)
这与循环展开有些相关。
int main()
{
for (int i = 0; i <= 16; ++i) // CHANGED NUMBER OF ITERATIONS
std::sqrt(i);
}
被替换为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);
// ...
编译器和实际编号可以“证明”每个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);
// ...
如果迭代次数很大,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
因此它必须假定该编译指示是ON(否则生成的代码可能不正确)(情况更复杂,请参阅错误34678和Tavian Barnes的注释)。
原因是标准要求设置errno
以防sqrt
传递一个负数。 显然,对于值50(展开过多),g ++“忘记”负值不可能,而对于小值而言,展开并在每个单独情况下通过不断传播发现不需要设置errno
可能出现。
调用sqrt
之前取绝对值显然确保g ++,它是不可能有负面的论点传递。