rom*_*ric 30 c++ optimization performance gcc clang
使用相同编译器标志(或者)编译的以下代码clang
运行速度几乎快60倍:gcc
-O2
-O3
#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;
}
Run Code Online (Sandbox Code Playgroud)
我已经测试了三个gcc
版本4.8.4/4.9.2/5.2.1
和两个clang
版本3.5.1/3.6.1
,这是我的机器上的时间(for gcc 5.2.1
和clang 3.6.1
):
时间安排-O3
:
gcc: 2.41888s
clang: 0.0396217s
Run Code Online (Sandbox Code Playgroud)
时间安排-O2
:
gcc: 2.41024s
clang: 0.0395114s
Run Code Online (Sandbox Code Playgroud)
时间安排-O1
:
gcc: 2.41766s
clang: 2.43113s
Run Code Online (Sandbox Code Playgroud)
因此,gcc
即使在更高的优化级别,似乎也不会优化此功能.我的装配输出clang
差不多大约100行gcc
,我不认为有必要在这里发布,我只能说在gcc
装配输出中有一个调用pow
没有出现在clang
装配中,可能是因为clang
它优化了一堆固有的电话.
由于结果相同(即i = 6966764.74717416727754
),问题是:
gcc
不能优化这个功能clang
呢?k
to 的值1.0
并gcc
变得一样快,是否存在gcc
无法绕过的浮点运算问题?我确实试过static_cast
并打开警告,看看隐式转换是否存在任何问题,但事实并非如此.
更新:为了完整性,这里是结果-Ofast
gcc: 0.00262204s
clang: 0.0013267s
Run Code Online (Sandbox Code Playgroud)
关键是gcc
不优化代码O2/O3
.
Sha*_*our 33
从这个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
Run Code Online (Sandbox Code Playgroud)
并且它展开内循环:
.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)
Run Code Online (Sandbox Code Playgroud)
注意,它在编译时使用内置函数(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 #,
Run Code Online (Sandbox Code Playgroud)
虽然这是一个更简单的案例.
如果更改条件内环到n < 4
随后的gcc似乎愿意以优化的时候k = 0.7
.正如对问题的评论中所指出的,如果编译器不相信展开将有所帮助,那么由于存在代码大小权衡,它将展开多少将是保守的.
正如评论中所示,我在使用Godbolt示例中使用OP代码的修改版本,但它并未改变基本结论.
请注意,如果我们使用-fno-math-errno,如上面的注释所示,停止errno
设置,gcc会应用类似的优化.