我有简单的程序:
#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和don的结果不做任何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); }
被替换为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-fully-peeled-insns = x –param max-completely-peel-times = y)
>并不“足够聪明”来确定对sqrt(i)的调用没有副作用(但是一个小的帮助就足够了,例如std :: sqrt(std :: abs(i))).
另外gcc(v6.x)不支持#pragma STDC FENV_ACCESS,所以它必须假设这个编译指示是ON(否则生成的代码可能不正确)(情况比较复杂,见bug 34678和Tavian Barnes’ comment).