为什么 GCC 不能优化出 'std::sqrt'?
Why can't GCC optimize out `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
如果我正确理解,就好像规则一样,编译器可以优化出不会改变程序可观察行为的代码,包括I/O写入等。我放弃std::sqrt
的结果,不进行任何I/O。此外,我的程序中没有#pragma STDC FENV_ACCESS
。std::sqrt
是否有可观察到的副作用,或者GCC没有优化调用的另一个原因是什么?
(这个问题的最初版本有一个10e50
的上界,使其成为一个无限循环。50
也会发生同样的事情,所以请对此发表评论。)
这在某种程度上与循环展开有关。
int main()
{
for (int i = 0; i <= 16; ++i) // CHANGED NUMBER OF ITERATIONS
std::sqrt(i);
}
被替换为CCD_ 7(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
原因是标准要求在sqrt
传递负数的情况下设置errno
。显然,对于值50(对于展开来说太多),g++"忘记"负值是不可能的,而对于较小的值,则展开并通过不断传播发现在每种情况下都可能不需要设置errno
。
在调用sqrt
之前取绝对值显然可以确保g++不可能有一个负参数要传递。
相关文章:
- CMake项目Boost库错误:Boost/config/compiler/gcc.hpp:165:10:致命错误:cs
- 奇怪的结构&GCC&clang(void*返回类型)
- GCC本机矩阵运算库
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- gcc和c++17的过载解析失败
- 数据成员SFINAE的C++17测试:gcc vs clang
- GCC对可能有效的代码抛出init list生存期警告
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 使用 GCC 卸载的 OpenMP 卸载失败,并出现"Ptx assembly aborted due to errors"
- 为什么与常规GCC不同,即使有"学究性错误",MinGW-GCC也能容忍丢失的返回类型
- 使用gcc从静态链接的文件中查找可选符号
- 普通环路未使用gcc 4.8.5自动矢量化
- 有了gcc,是否可以链接库,但前提是它存在
- 在clang++预处理器中确定gcc工具链版本
- 为什么 gcc 编译这个而 msvc 没有
- 求出有多少个数字是完美平方,而sqrt()是L,R范围内的素数
- 为什么lambda在clang上崩溃而不是在gcc上崩溃
- CMake 无法检测 gcc 内置函数,如 sqrt、pow、exp 等
- 为什么 GCC 调用 libc 的 sqrt() 而不使用其结果?
- 为什么 GCC 不能优化出 'std::sqrt'?