![](/img/trans.png)
[英]Why does GCC generate a faster program than Clang in this recursive Fibonacci code?
[英]Why does clang produce a much faster code than gcc for this simple function involving exponentiation?
使用clang
编译的以下代码比使用具有相同编译器标志( -O2
或-O3
)的gcc
编译的代码运行快近60倍:
#include <iostream>
#include <math.h>
#include <chrono>
#include <limits>
long double func(int num)
{
long double i=0;
long double k=0.7;
for(int t=1; t<num; t++){
for(int n=1; n<16; n++){
i += pow(k,n);
}
}
return i;
}
int main()
{
volatile auto num = 3000000; // avoid constant folding
std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();
auto i = func(num);
end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed = end-start;
std::cout.precision(std::numeric_limits<long double>::max_digits10);
std::cout << "Result " << i << std::endl;
std::cout << "Elapsed time is " << elapsed.count() << std::endl;
return 0;
}
我用三个gcc
版本4.8.4/4.9.2/5.2.1
和两个clang
版本3.5.1/3.6.1
测试了这个,这是我机器上的时间(对于gcc 5.2.1
和clang 3.6.1
) :
时间-O3
:
gcc: 2.41888s
clang: 0.0396217s
时间-O2
:
gcc: 2.41024s
clang: 0.0395114s
时间-O1
:
gcc: 2.41766s
clang: 2.43113s
因此,即使在更高的优化级别, gcc
似乎也不会优化此功能。 clang
的组装输出几乎比gcc
大约100行,我认为没有必要在这里发布,我只能说在gcc
汇编输出中有一个调用pow
,它不会出现在clang
程序集中,大概是因为clang
它优化为一堆内在的调用。
由于结果相同(即i = 6966764.74717416727754
),问题是:
gcc
不能在clang
时优化这个功能? k
的值更改为1.0
并且gcc
变得一样快,是否存在gcc
无法绕过的浮点运算问题? 我确实尝试过static_cast
并打开警告,看看隐式转换是否有任何问题,但不是真的。
更新:为了完整性,这里是-Ofast
的结果
gcc: 0.00262204s
clang: 0.0013267s
关键是gcc
没有优化O2/O3
的代码。
从这个godbolt会话中, clang能够在编译时执行所有的pow
计算。 它在编译时知道k
和n
的值是什么,它只是常数折叠计算:
.LCPI0_0:
.quad 4604480259023595110 # double 0.69999999999999996
.LCPI0_1:
.quad 4602498675187552091 # double 0.48999999999999994
.LCPI0_2:
.quad 4599850558606658239 # double 0.34299999999999992
.LCPI0_3:
.quad 4597818534454788671 # double 0.24009999999999995
.LCPI0_4:
.quad 4595223380205512696 # double 0.16806999999999994
.LCPI0_5:
.quad 4593141924544133109 # double 0.11764899999999996
.LCPI0_6:
.quad 4590598673379842654 # double 0.082354299999999963
.LCPI0_7:
.quad 4588468774839143248 # double 0.057648009999999972
.LCPI0_8:
.quad 4585976388698138603 # double 0.040353606999999979
.LCPI0_9:
.quad 4583799016135705775 # double 0.028247524899999984
.LCPI0_10:
.quad 4581356477717521223 # double 0.019773267429999988
.LCPI0_11:
.quad 4579132580613789641 # double 0.01384128720099999
.LCPI0_12:
.quad 4576738892963968780 # double 0.0096889010406999918
.LCPI0_13:
.quad 4574469401809764420 # double 0.0067822307284899942
.LCPI0_14:
.quad 4572123587912939977 # double 0.0047475615099429958
并且它展开内循环:
.LBB0_2: # %.preheader
faddl .LCPI0_0(%rip)
faddl .LCPI0_1(%rip)
faddl .LCPI0_2(%rip)
faddl .LCPI0_3(%rip)
faddl .LCPI0_4(%rip)
faddl .LCPI0_5(%rip)
faddl .LCPI0_6(%rip)
faddl .LCPI0_7(%rip)
faddl .LCPI0_8(%rip)
faddl .LCPI0_9(%rip)
faddl .LCPI0_10(%rip)
faddl .LCPI0_11(%rip)
faddl .LCPI0_12(%rip)
faddl .LCPI0_13(%rip)
faddl .LCPI0_14(%rip)
注意,它使用内置函数( gcc在这里记录它们 )在编译时计算pow
,如果我们使用-fno-builtin它不再执行此优化。
如果将k
更改为1.0
则gcc可以执行相同的优化:
.L3:
fadd %st, %st(1) #,
addl $1, %eax #, t
cmpl %eax, %edi # t, num
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
fadd %st, %st(1) #,
jne .L3 #,
虽然这是一个更简单的案例。
如果将内循环的条件更改为n < 4
则gcc似乎愿意在k = 0.7
时进行优化 。 正如对问题的评论中所指出的,如果编译器不相信展开将有所帮助,那么由于存在代码大小权衡,它将展开多少将是保守的。
正如评论中所示,我在使用Godbolt示例中使用OP代码的修改版本,但它并未改变基本结论。
注意如上面的注释中所示,如果我们使用-fno-math-errno ,它会阻止errno
被设置,gcc会应用类似的优化 。
除了Shafik Yaghmour的回答之外,我想指出你在变量num
上使用volatile
似乎没有效果的原因是在func
被调用之前读取了num
。 无法优化读取,但仍可以优化函数调用。 如果你声明func
的参数是对volatile
的引用,即。 long double func(volatile int& num)
,这会阻止编译器优化掉对func
的整个调用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.