Clang和GCC - 哪个可以生成更快的二进制文件?

294
我目前正在使用GCC,但我最近发现了Clang,并考虑转换。然而有一个决定性的因素-生成的二进制文件的质量(速度、内存占用和可靠性) - 如果gcc -O3可以生成1%更快运行的二进制文件,或者Clang二进制文件的内存占用更大,或仅因编译器错误而失败,则这将成为一项交易破坏者。
Clang声称比GCC具有更快的编译速度和更低的编译时内存占用,但我真正关心的是已编译软件的基准/比较结果-您能指向一些现有资源或您自己的基准测试吗?

12
这个问题正在元社区上讨论。 - cigien
7个回答

296
以下是我对GCC 4.7.2和Clang 3.2进行的最新研究结果,尽管范围较窄,涉及C++。
更新:附加了GCC 4.8.1与clang 3.3的比较结果。
更新:GCC 4.8.2与clang 3.4的比较结果也已附加。
我维护一个开源软件工具,使用GCC和Clang构建Linux版本,使用Microsoft的编译器构建Windows版本。该工具名为coan,是C/C++源文件和代码行的预处理器和分析器。它的计算特点主要是递归下降解析和文件处理。目前,这些结果所涉及的开发分支大约包括90个文件中的11K行代码。它采用丰富的多态性和模板编写的C++,但仍然受到其不久之前在拼凑的C代码中的许多补丁的影响。没有明确利用移动语义。它是单线程的。我没有投入任何严肃的努力来优化它,而“架构”仍然在待办事项列表中。

我之前只把Clang作为实验性编译器使用,因为尽管它的编译速度和诊断功能更优秀,但其对C++11标准的支持在coan所涉及的方面上仍落后于当代GCC版本。但是,从3.2开始,这一差距已经被弥合。

我的Linux测试环境用于当前coan开发过程中,大约有70K个源文件,包括单文件解析器测试用例、消耗1000个文件的压力测试和消耗<1K文件的场景测试。

除了报告测试结果外,测试环境还累计并显示了coan消耗的文件总数和运行时间(它只是将每个coan命令行传递给Linux的time命令,并捕获和累加报告的数字)。由于任何需要0可测量时间的测试数量都会相加得到0,因此计时统计数据会受到影响,但是这些测试的贡献可以忽略不计。计时统计数据会像这样在make check结束时显示:

coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.

我对比了GCC 4.7.2和Clang 3.2的测试框架性能,除了编译器以外其他所有条件都是相同的。从Clang 3.2开始,我不再需要在代码轨迹中进行预处理器区分,以便GCC可以编译而Clang可以使用替代方法。我在每种情况下都构建了同一C++库(GCC的),并在同一个终端会话中连续运行了所有比较。

我发布版本的默认优化级别为-O2。我还成功地测试了-O3版本的构建。我将每个配置进行了3次连续测试,并平均统计了3个结果,具体结果如下。数据单元格中的数字表示coan可执行文件处理每个约70K输入文件(读取、解析和写入输出和诊断)所消耗的平均微秒数。

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|

任何特定的应用程序都很可能具有不公平地发挥编译器优势或弱点的特征。严格的基准测试采用多种应用程序。考虑到这一点,这些数据的值得注意的特点是:
-O3 优化对 GCC 的影响微不足道。
-O3 优化对 Clang 非常有益。
在 -O2 优化时,GCC 稍微快于 Clang。
在 -O3 优化时,Clang 明显比 GCC 更快。
一次偶然的发现,进一步有趣的比较了这两个编译器。Coan广泛使用智能指针,在文件处理中一个特定的智能指针类型被大量使用。为了区分编译器,这个特定的智能指针类型在之前的版本中已经进行了typedef,如果配置的编译器对其使用具有足够成熟的支持,则将其定义为std::unique_ptr<X>,否则为std::shared_ptr<X>。偏向于std::unique_ptr是愚蠢的,因为这些指针实际上是传递的,但在我还不熟悉C++11变体时,std::unique_ptr看起来更适合替换std::auto_ptr
在进行试验性构建以评估Clang 3.2对此类区分的持续需求时,我无意中构建了std :: shared_ptr<X>,而我本打算构建std :: unique_ptr<X>,令人惊讶的是,结果可执行文件在默认-O2优化下是我所见过最快的,有时每个输入文件可达到184毫秒。通过对源代码进行这个更改,相应的结果如下:
          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |

这里需要注意以下几点:
  1. 现在无论是哪个编译器,都不会从-O3优化中获益。
  2. Clang在每个优化级别上的表现都比GCC更好。
  3. GCC的性能只受到智能指针类型更改的轻微影响。
  4. Clang的-O2性能受智能指针类型更改的重要影响。
在智能指针类型更改之前和之后,Clang能够以-O3优化构建一个明显更快的coan可执行文件,在最适合该任务的情况下,它可以在-O2和-O3时构建同样更快的可执行文件,使用的指针类型是std::shared_ptr<X>
一个显而易见的问题是,我不具备评论的能力,即为什么当将大量使用的智能指针类型从unique更改为shared时,Clang能够在我的应用程序中找到25%的-O2加速,而GCC对相同的更改漠不关心。我也不知道是否应该为发现Clang的-O2优化对我的智能指针选择的智慧具有如此巨大的敏感度而欢呼或抱怨。 更新:GCC 4.8.1与clang 3.3

现在对应的结果是:

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |

所有四个可执行文件现在处理1个文件的平均时间比以前要长得多,这并不反映最新编译器的性能。这是因为测试应用程序的后期开发分支在此期间已经具有了许多解析复杂性,并因速度而付出代价。只有比率是重要的。
现在需要注意的要点并不是很新颖:
- GCC对-O3优化不感兴趣 - clang从-O3优化中获益非常微小 - 在每个优化级别上,clang击败GCC同样重要的差距。
将这些结果与GCC 4.7.2和clang 3.2的结果进行比较,可以看出GCC在每个优化级别上已经收回了clang约四分之一的领先优势。但由于测试应用程序在此期间得到了大量开发,因此无法自信地将其归因于GCC代码生成的追赶。(这次,我已经记录下获取时间的应用程序快照,并可以再次使用它。)
更新:GCC 4.8.2 v clang 3.4
我完成了GCC 4.8.1 v Clang 3.3的更新,表示我将继续使用相同的coan快照进行进一步更新。但我决定改为在该快照(rev. 301)和最新的开发快照上进行测试(rev. 619)。这样可以得到更长时间的结果,而且我还有另一个动机:
我的原始帖子指出,我没有投入任何精力来优化coan的速度。截至rev. 301,情况仍然如此。然而,在我将计时装置建立到coan测试工具中后,每次运行测试套件时,最新更改的性能影响就会显现出来。我发现它通常会出乎意料地大,并且趋势比我认为的功能增益所值得的更加陡峭。

在修订版308中,测试套件中每个输入文件的平均处理时间比第一次发布时增加了一倍以上。这时,我改变了10年来不关心性能的政策。在修订版本619的密集修改中,性能始终是一个考虑因素,其中许多修改纯粹是为了在基本上更快的线路上重写关键负载承受者(尽管没有使用任何非标准编译器功能)。看到每个编译器对这个政策的反应会很有趣。

这里是最新两个编译器版本(修订版301)的时间矩阵:

coan - rev.301 results

          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|

The story here指的是GCC-4.8.1和Clang-3.3之间的差异,两者表现略有不同,GCC稍微好一些,Clang稍微差一些,这可能是噪声造成的。在-O2和-O3方面,Clang仍然比GCC更出色,虽然在大多数应用程序中这不重要,但对于很多应用程序来说却很重要。以下是rev. 619的矩阵。
          | -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|

将301和619的数字并排放置,几个要点显而易见。

  • 我旨在编写更快的代码,两个编译器都坚定地证明了我的努力。但是:

  • GCC比Clang更慷慨地回报了这些努力。在-O2优化下,Clang的619构建比其301构建快46%:在-O3下,Clang的改进为31%。很好,但在每个优化级别上,GCC的619构建都比其301构建快两倍以上。

  • GCC完全扭转了Clang以前的优越性。在每个优化级别上,GCC现在比Clang快17%。

  • Clang在301版本中从-O3优化中获得更多杠杆作用的能力,在619版本中已经消失。两个编译器都没有从-O3中获得有意义的收益。

我对这种命运的逆转感到惊讶,怀疑自己可能意外地制作了一个缓慢的clang 3.4版本(因为我是从源代码构建的)。因此,我使用发行版默认的Clang 3.3重新运行了619个测试。结果与3.4版本几乎相同。
关于对这个反转的反应:就这里的数据而言,当我没有提供帮助时,Clang在从我的C++代码中挤出速度方面做得比GCC好得多。当我专注于提供帮助时,GCC的表现要比Clang好得多。
我不会把这个观察结果提升为一个原则,但我明白“哪个编译器生成更好的二进制文件?”这个问题即使你指定了它所相对应的测试套件,仍然不是一个清晰明确的时间选择问题。
你的更好的二进制文件是最快的二进制文件,还是最好地弥补了便宜的代码?或者最能弥补花费昂贵的重复使用的代码而不是速度?这取决于你生产二进制文件的动机的性质和相对权重以及你在这样做时的限制。

无论如何,如果你非常关心构建“最好的”二进制文件,那么你最好不断检查编译器的连续迭代如何在连续迭代的代码中实现你对“最好”的理念。


12
为什么Clang更快?例如,英特尔编译器使用英特尔芯片的专业功能。Clang使用什么来获得优势?代码是否可以重写以使GCC具有相同的性能? - kirill_igum
41
GCC和clang是由不同团队的程序员编写的巨大复杂的程序,用于将源代码转换为目标代码。在任何选定的测试中,在任何时间点上,它们中的一个几乎不可避免地会比另一个做得更好。获胜者并不一定要使用什么特别的“东西”来“获取优势”,而且由于这两个程序都是开源的,它们之间没有任何秘密。 - Mike Kinghan
4
可以使用 kcachegrind 工具来确定生成的可执行文件性能差异的函数。 - user811773
1
Mike:当你进行优化工作时,你是使用gcc编译器、clang还是两者都用?我预计你使用的编译器会从有针对性的优化工作中获得最大的改进。 - David Stone
1
@DavidStone 我在常规的编辑/构建/测试循环中使用clang,因为它编译速度更快,但是每当我构建软件包并运行 make check(带计时)时,我会同时使用gcc和clang。 - Mike Kinghan
显示剩余8条评论

52

Phoronix进行了一些基准测试,但这是关于几个月前Clang / LLVM的快照版本。结果是两者都没有明显领先,在所有情况下,GCC和Clang都没有绝对的优劣之分。

考虑到您将使用最新的Clang,这也许就不那么重要了。另一方面,据说GCC 4.6将为Core 2Core i7进行一些主要的优化

我认为Clang更快的编译速度对于原始开发人员来说更加方便,然后当您将代码推向世界,Linux发行版,BSD等终端用户将使用GCC以获得更快的二进制文件。


2
就在今天,我对Clang编译速度进行了一些基准测试,对于纯C语言来说,结果令人非常失望。使用Clang编译35个C文件,总共270 KLOC的代码,只比传统编译器快25%。当我看到Linux上TinyCC的速度有多快时,这对于一个新开发的编译器来说是一个糟糕的结果。当使用优化选项-O2/-O3时,情况会有所改善,但由于这些选项通常用于发布版本的构建,编译器性能在这种情况下并不重要。 - Lothar
7
也许 Nietzsche-jou 是用 Clang 编译的,而你是用 GCC 编译的。 - Mateen Ulhaq
未来的读者应该查看 Phoronix 获取最新文章。例如,https://www.phoronix.com/scan.php?page=article&item=aocc32-clang-gcc&num=1 查看 AMD Zen CPU 上 AOCC vs. GCC vs. clang 的比较,或者 https://www.phoronix.com/scan.php?page=article&item=11900k-gcc11-clang12&num=1 查看 Intel i9-11900K(Rocket Lake,Ice Lake 的 14nm 回归版)上 GCC11 vs. clang12 的比较。 - Peter Cordes

21

Clang编译代码的速度更快这个事实可能并不像生成的二进制文件的速度那样重要。不过,在这里有一组基准测试数据


15
实际上确实如此。在开发过程中,编译时间(以及由于编译而消耗的资源)比二进制性能更加成为瓶颈。毕竟,在这个阶段我们在调试模式下进行编译。只有在测试和发布阶段,您才会切换到发布模式,并尝试获得尽可能快的二进制文件。 - Matthieu M.
4
@ Matthieu M: 我发誓该回答说的是“可能..”,好像他正在提出一个潜在的问题。我猜这可能值得提一下,因为它与OP相关。 - J. M. Becker
同意,这里提出了很多好观点。我宁愿加入第二或第三个RAID 0硬盘、固态硬盘或更多更快的内存,并获得最佳的.exe性能——只要这些措施能够使你达到或接近平衡。有时候使用多个编译器会很有帮助,可以让你了解非可移植的特性,并捕获否则无法检测到的错误,或导致浪费数天时间尝试调试一个更好的编译器会警告/报错的代码。 - user1899861
今天我尝试比较了一些我写的性能关键整数代码,使用-O2和-O3编译选项,GCC运行速度更快(22秒clang-llvm 25秒)。使用编译器开关(gcc或clang)可以覆盖大多数非标准特性和静态警告。在你自己的大型项目中,如果编译时间占主导地位,而不是链接时间,那么你的构建系统可能存在问题。如果经常进行make clean,则有像https://ccache.samba.org/这样的工具可以帮助解决问题。更换编译器的另一个问题是,所有测试/验证的时间投资都将被抛弃。 - Rob11311
https://code.google.com/p/distcc/ 是另一个项目,可以加速批量编译时间,如果整个库需要重新编译,由于数据结构的更改或验证/验证目的。 - Rob11311

13

就二进制文件的速度而言,GCC 4.8和Clang 3.3之间几乎没有什么明显的差异。在大多数情况下,两个编译器生成的代码表现相似。这两个编译器都没有占据优势。

声称GCC和Clang之间存在显著性能差距的基准测试是偶然的。

编译器的选择会影响程序的性能。如果开发人员或一组开发人员专门使用GCC,则可以预期使用GCC比使用Clang稍微快一些,反之亦然。

从开发人员的角度来看,GCC 4.8+和Clang 3.3之间一个值得注意的区别是GCC具有-Og命令行选项。此选项启用不会干扰调试的优化,因此例如始终可以获得准确的堆栈跟踪。Clang中缺少此选项使得Clang对于某些开发人员作为优化编译器更难使用。


最近,我发现在编译时间为10秒至30秒之间的程序中,3.3和4.8之间的编译时间差异并不大。 - alfC

10
我注意到GCC 5.2.1和Clang 3.6.2之间的一个奇怪差异是,如果您有一个关键循环,例如:
for (;;) {
    if (!visited) {
        ....
    }
    node++;
    if (!*node)
        break;
}

然后,当使用-O3-O2编译时,GCC会推测性地将循环展开八次。Clang则不会展开。通过试错,我发现在我的特定程序数据情况下,正确的展开次数是五次,所以GCC过度展开,而Clang则低估了。然而,过度展开对性能的影响更大,因此GCC在这里表现得更差。
完全不知道展开差异是否是一种普遍趋势,还是仅适用于我的情况。
前段时间,我写了几个垃圾收集器来学习更多关于C语言性能优化的知识。结果让我稍微倾向于Clang。特别是因为垃圾收集主要涉及指针追踪和内存复制。
结果如下(秒数):
+---------------------+-----+-----+
|Type                 |GCC  |Clang|
+---------------------+-----+-----+
|Copying GC           |22.46|22.55|
|Copying GC, optimized|22.01|20.22|
|Mark & Sweep         | 8.72| 8.38|
|Ref Counting/Cycles  |15.14|14.49|
|Ref Counting/Plain   | 9.94| 9.32|
+---------------------+-----+-----+

这是纯C代码,我并不声称编译C++代码时编译器的性能。

在处理器为Phenom II X6 1090T,AMD,操作系统为Ubuntu 15.10 (Wily Werewolf)的x86.64架构上运行。


就我所知,展开循环在某些CPU上可以加快代码速度,在其他CPU上则可能会减慢速度;我希望GCC不要这样做,除非你使用“-march=X”选项。 - hanshenrik

7
唯一确定这一点的方法是尝试。值得一提的是,我见过使用苹果的LLVM GCC 4.2相比于常规GCC 4.2(用于具有大量SSE的x86-64代码)有很好的改进,但对于不同的代码库可能会有所不同。假设你正在使用x86 / x86-64,并且你确实关心最后的几个百分点,那么你也应该尝试英特尔ICC,因为它经常能够击败GCC - 你可以从intel.com获得30天的评估许可证并尝试它。

5

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