为什么gcc在这个递归的斐波那契代码中生成比clang更快的程序?

时间:2015-03-21 18:08:35

标签: c++ gcc clang compiler-optimization

这是我测试过的代码:

#include <iostream>
#include <chrono>
using namespace std;

#define CHRONO_NOW                  chrono::high_resolution_clock::now()
#define CHRONO_DURATION(first,last) chrono::duration_cast<chrono::duration<double>>(last-first).count()

int fib(int n) {
    if (n<2) return n;
    return fib(n-1) + fib(n-2);
}

int main() {
    auto t0 = CHRONO_NOW;
    cout << fib(45) << endl;
    cout << CHRONO_DURATION(t0, CHRONO_NOW) << endl;
    return 0;
}

当然,有更快的方法来计算斐波纳契数,但这是一个很好的小压力测试,专注于递归函数调用。 除了使用计时器测量时间之外,代码没有别的了。

首先,我使用-O3优化在OS X上的Xcode中进行了几次测试(这样就是铿锵声)。跑了大约9秒钟。

然后,我在Ubuntu上用gcc(g ++)编译了相同的代码(再次使用-O3),该版本只用了大约6.3秒就可以运行了!另外,我在我的Mac上运行Ubuntu 在VirtualBox 中,这只会对性能产生负面影响(如果有的话)。

所以你去了:

在OS X上打字 - &gt; ~9秒

Uccntu上的gcc - &gt; ~6.3秒。

我知道这些是完全不同的编译器,所以他们做的事情不同,但我看到的所有gcc和clang的测试只显示出更少的差异,在某些情况下,差异是另一种方式(铿锵快。)

有没有合理的解释为什么gcc在这个特定的例子中以英里为单位击败?

2 个答案:

答案 0 :(得分:8)

compiler explorer中的GCC 4.9.2实际上是循环展开并且内联大量函数调用,而Clang 3.5.1每次迭代调用fib 两次,甚至没有{{ 3}}如下所示

fib(int):                                # @fib(int)
        push    rbp
        push    rbx
        push    rax
        mov     ebx, edi
        cmp     ebx, 2
        jge     .LBB0_1
        mov     eax, ebx
        jmp     .LBB0_3
.LBB0_1:
        lea     edi, dword ptr [rbx - 1]
        call    fib(int)       # fib(ebx - 1)
        mov     ebp, eax
        add     ebx, -2
        mov     edi, ebx
        call    fib(int)       # fib(ebx - 2)
        add     eax, ebp
.LBB0_3:
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret

GCC版本的时间延长了10倍以上,只有一个fib来电和20多个用于内联通话的标签,这也意味着最后一次通话是优化到jmp或者它已经将一些递归转换为迭代(因为它分配了一个大数组来存储中间值)

我也提到了ICC,令人惊讶的是它在call内有10条fib条指令,并且{strong>内联fibmain内调用了9次} ,但它不会将递归代码转换为迭代

tail call optimization

请注意,您可以像这样修改代码以使输出更易于阅读

int fib(int n) {
    if (n<2) return n;
    int t = fib(n-1);
    return t + fib(n-2);
}

现在编译器资源管理器将突出显示汇编输出中的指令与不同颜色对应的源代码行,并且您将很容易看到这两个调用是如何进行的。行return t + fib(n-2)由GCC编译为

.L3:
        mov     eax, DWORD PTR [rsp+112]  # n, %sfp
        add     edx, DWORD PTR [rsp+64]   # D.35656, %sfp
        add     DWORD PTR [rsp+60], edx   # %sfp, D.35656
        sub     DWORD PTR [rsp+104], 2    # %sfp,

答案 1 :(得分:3)

我不会说gcc比几英里更胜一筹。在我看来,性能差异(6.3秒对9秒)相当小。在我的FreeBSD系统上,clang需要26.12秒,gcc需要10.55秒。

但是,调试方法是使用g ++ -S和clang ++ -S来获取程序集输出。

我在FreeBSD系统上测试了这个。汇编语言文件太长了,不能在这里发布,但似乎gcc在fibonacci计算函数中执行多个内联级别(那里有20个fib()调用!)而clang只是调用fib(n-1)和fib (n-2)没有内联级别。

顺便说一句,我的gcc版本是4.2.1 20070831补丁[FreeBSD],而clang版本是3.1(branches / release_31 156863)20120523。这些是FreeBSD 9.1-RELEAESE基本系统附带的版本。 CPU是AMD Turion II Neo N40L双核处理器(1497.54-MHz)。