为什么clang产生比gcc快得多的代码用于涉及求幂的这个简单函数?

时间:2015-10-27 01:26:21

标签: c++ performance gcc optimization clang

使用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),问题是:

  1. 为什么i = 6966764.74717416727754可以gcc无法优化此功能?
  2. clang的值更改为k1.0变得一样快,是否存在gcc无法绕过的浮点运算问题?
  3. 我确实尝试了gcc并启用了警告,看看隐式转换是否存在任何问题,但实际上并非如此。

    更新:为了完整起见,这里是static_cast

    的结果
    -Ofast

    重点是gcc: 0.00262204s clang: 0.0013267s 不会优化gcc处的代码。

2 个答案:

答案 0 :(得分:33)

从这个godbolt session clang能够在编译时执行所有pow计算。它在编译时知道kn的值是什么,它只是常数折叠计算:

.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)的整个调用。