clang和gcc的区别

27

我在不同的项目中都使用了这两个编译器。

它们在代码处理和输出生成方面有什么不同呢?例如,gccclang都有用于优化的-O2选项。它们在优化代码方面是否以相同的方式(高级别)操作?我进行了一些测试,例如如果我有以下代码:

int foo(int num) {
    if(num % 2 == 1)
        return num * num;
    else
        return num * num +1;  
}

以下是使用clang和gcc -O2选项的输出汇编:

----gcc 5.3.0-----                              ----clang 3.8.0----
foo(int):                                       foo(int):
        movl    %edi, %edx                              movl    %edi, %eax
        shrl    $31, %edx                               shrl    $31, %eax
        leal    (%rdi,%rdx), %eax                       addl    %edi, %eax
        andl    $1, %eax                                andl    $-2, %eax
        subl    %edx, %eax                              movl    %edi, %ecx
        cmpl    $1, %eax                                subl    %eax, %ecx
        je      .L5                                     imull   %edi, %edi
        imull   %edi, %edi                              cmpl    $1, %ecx
        leal    1(%rdi), %eax                           setne   %al
        ret                                             movzbl  %al, %eax
.L5:                                                    addl    %edi, %eax
        movl    %edi, %eax                              retq
        imull   %edi, %eax
        ret

正如你所看到的,输出结果具有不同的指令。那么我的问题是,它们中的一个在不同的项目中是否更具优势?


3
你可以通过编写 int foo(int num) { return num * num + ~num & 1; } 来改进你的代码。 - fuz
1
@FUZxxl:非常好的观点,这使代码更好,但你需要括号 int foo(int num) { return num * num + (~num & 1); } 因为 & 的优先级低于 * 和 +。此外,对于负数,它确实具有不同的行为。在 C 中,-1 % 2-1,因此 if 语句为 false。如果您编写 n*n + (n%2),gcc 的代码会考虑到这一点。 - Peter Cordes
@FUZxxl,感谢您的留言。这只是一个简单的测试,以查看哪些编译器输出什么。 - Pooya
@PeterCordes 对,你当然是正确的。 - fuz
5
@FUZxxl return num * num + ~num & 1; 你在开玩笑吗?那段代码很令人困惑,你在单行中犯了一个错误,原始代码对于更多的人来说更安全易维护。 - user1135541
2个回答

25

是的。但并不完全正确。

这就像询问奥迪车是否比梅赛德斯车更具优势。与它们类似,这两个编译器是旨在完成相同任务的两个不同项目。在某些情况下,gcc会生成更好的代码,而在其他情况下,clang则会优于它。

当你需要知道时,你必须使用这两个编译器来编译你的代码,然后进行测量。

这里有一个论点在这里还有一个与此较少相关的在这里


17

在这种情况下,Clang的输出更好,因为它不会使用分支,而是将num%2 == 1的值加载到al中。GCC生成的代码使用跳转操作。如果预期num有50%的机会是偶数/奇数,并且没有重复模式,则由GCC生成的代码将容易受到分支预测失败的影响。


但是,您也可以通过执行以下操作使代码在GCC上也表现良好:

int foo(int num) {
    return num * num + (num % 2 != 1);
}

更重要的是,由于您的算法似乎只适用于无符号数,因此您应该使用unsigned int(它们对于负数是不同的)- 实际上,通过将unsigned int用于参数,您可以获得主要的加速,因为现在GCC / Clang可以将num%2优化为num&1

unsigned int foo(unsigned int num) {
    return num * num + (num % 2 != 1);
}

gcc -O2 生成的代码结果

movl    %edi, %edx
imull   %edi, %edi
andl    $1, %edx
xorl    $1, %edx
leal    (%rdi,%rdx), %eax
ret

因此,一个编译器并不像一个知道自己在做什么的程序员那样重要。新生成的函数代码比任何编译器生成的原始函数代码都要好得多。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接