为什么clang产生比gcc更快的代码,这个涉及求幂的简单函数?

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.1clang 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),问题是:

  1. 为什么gcc不能优化这个功能clang呢?
  2. 更改kto 的值1.0gcc变得一样快,是否存在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在编译时执行所有计算.它知道在编译时什么的价值观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
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会应用类似的优化.