使用clang
编译的以下代码运行速度比使用gcc
编译的编译器标志(-O2
或-O3
)快几乎快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
和{ {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
大约100行,我认为没有必要在此发布,我只能说gcc
汇编输出那里是对gcc
的调用,它没有出现在pow
汇编中,大概是因为clang
将其优化为一堆内在调用。
由于结果相同(即clang
),问题是:
i = 6966764.74717416727754
可以gcc
无法优化此功能?clang
的值更改为k
,1.0
变得一样快,是否存在gcc
无法绕过的浮点运算问题? 我确实尝试了gcc
并启用了警告,看看隐式转换是否存在任何问题,但实际上并非如此。
更新:为了完整起见,这里是static_cast
-Ofast
重点是gcc: 0.00262204s
clang: 0.0013267s
不会优化gcc
处的代码。
答案 0 :(得分:33)
从这个godbolt session 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 documents theirs here )在编译时计算pow
,如果我们使用-fno-builtin它不再执行此操作优化
如果您将k
更改为1.0
,然后将gcc is able to perform更改为相同的优化:
.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
时将内循环的条件更改为k = 0.7
,则gcc seems willing to optimize更改为errno
。正如对问题的评论中所指出的,如果编译器不相信展开将有所帮助,那么由于代码大小权衡,它将展开多少可能是保守的。
如评论中所示,我在使用Godbolt示例中使用OP代码的修改版本,但它并未改变基本结论。
如果我们使用comment above,-fno-math-errno注明,function *g (a1,a2) {yield a1 && (yield a2);}
停止var i = 0;
function increase(){
setInterval(function () {
document.getElementById("loading").style.width = i + "%";
i++;
increase();
}, 1000);
}
设置,则gcc会应用a similar optimization。
答案 1 :(得分:1)
除了Shafik Yaghmour的回答之外,我还想指出你在变量volatile
上使用num
似乎没有效果的原因是{在num
被调用之前读取{1}}。读取不能被优化掉,但仍可以优化函数调用。如果您声明参数func
是对func
的引用,即。 volatile
,这会阻止编译器优化对long double func(volatile int& num)
的整个调用。