使用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
ing并打开了警告,看看隐式转换是否有任何问题,但不是真的。
更新:为了完整起见,这里是-Ofast
的结果
gcc: 0.00262204s
clang: 0.0013267s
关键是gcc
不会优化O2/O3
处的代码。
从这个golbolt会话中,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
如注释所示,我在godbolt示例中使用了OP代码的修改版本,但它不会改变基本结论。
请注意,如上面的注释所示,如果我们使用-fno-math-errno,它会阻止设置errno
,gcc确实应用了类似的优化。
除了Shafik Yaghmour的回答,我想指出的是,你在变量num
上使用易失性
看起来没有任何效果的原因是num
甚至在func
被调用之前就被读取了。读取不能被优化掉,但函数调用仍然可以被优化掉。如果你声明func
的参数是对易失性
的引用,即。long双func(易失性int