我目前正在使用 GCC,但我最近发现了 Clang,我正在考虑切换。但是有一个决定性因素——它生成的二进制文件的质量(速度、内存占用、可靠性)——如果gcc -O3
可以生成一个运行速度快 1% 的二进制文件,或者 Clang 二进制文件占用更多内存或只是由于编译器错误而失败,这是一笔交易-断路器。
与 GCC 相比,Clang 拥有更好的编译速度和更低的编译时内存占用,但我对生成的已编译软件的基准测试/比较非常感兴趣——您能指出一些预先存在的资源或您自己的基准测试吗?
我目前正在使用 GCC,但我最近发现了 Clang,我正在考虑切换。但是有一个决定性因素——它生成的二进制文件的质量(速度、内存占用、可靠性)——如果gcc -O3
可以生成一个运行速度快 1% 的二进制文件,或者 Clang 二进制文件占用更多内存或只是由于编译器错误而失败,这是一笔交易-断路器。
与 GCC 相比,Clang 拥有更好的编译速度和更低的编译时内存占用,但我对生成的已编译软件的基准测试/比较非常感兴趣——您能指出一些预先存在的资源或您自己的基准测试吗?
以下是我在 GCC 4.7.2 和 Clang 3.2 for C++ 中发现的一些最新的结果。
更新:GCC 4.8.1 v clang 3.3 比较附在下面。
更新:附加了 GCC 4.8.2 v clang 3.4 比较。
我维护了一个 OSS 工具,它是为 Linux 构建的,带有 GCC 和 Clang,以及 Microsoft 的 Windows 编译器。该工具coan是 C/C++ 源文件和代码行的预处理器和分析器:它的计算配置文件主要用于递归下降解析和文件处理。开发分支(与这些结果相关)目前在大约 90 个文件中包含大约 11K LOC。现在,它是用富含多态性和模板的 C++ 编码的,但由于它在 C 中的不那么遥远的过去仍然陷入了许多补丁中。移动语义没有被明确利用。它是单线程的。我没有认真地优化它,而“架构”仍然主要是 ToDo。
我仅将 3.2 之前的 Clang 用作实验性编译器,因为尽管它具有出色的编译速度和诊断能力,但它的 C++11 标准支持在 coan 所使用的方面落后于当代 GCC 版本。在 3.2 中,这个差距已经缩小。
我用于当前 coan 开发的 Linux 测试工具处理了大约 70K 源文件,混合了单文件解析器测试用例、消耗 1000 多个文件的压力测试和消耗 < 1K 文件的场景测试。
除了报告测试结果外,该工具还累积并显示在 coan 中消耗的文件总数和消耗的运行时间(它只是将每个 coan 命令行传递给 Linuxtime
命令并捕获并累加报告的数字)。由于任何数量为 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|
任何特定的应用程序很可能具有不公平地发挥编译器优势或劣势的特性。严格的基准测试采用不同的应用程序。考虑到这一点,这些数据的显着特征是:
在这些发现之后不久,偶然出现了对两个编译器的进一步有趣的比较。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>
了见过,有时达到 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 |
这里的注意点是:
在智能指针类型更改之前和之后,Clang 能够在 -O3 优化下构建一个显着更快的 coan 可执行文件,并且当该指针类型是最佳时,它可以在 -O2 和 -O3 构建一个同样更快的可执行文件std::shared_ptr<X>
-为了工作。
一个我没有能力评论的明显问题是,当大量使用的智能指针类型从唯一更改为共享时,为什么 Clang 应该能够在我的应用程序中找到 25% -O2 的加速,而 GCC 则无动于衷到同样的变化。我也不知道应该为 Clang 的 -O2 优化对我的智能指针选择的智慧如此敏感的发现欢呼还是嘘。
更新:GCC 4.8.1 v 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 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 的大量修订中,性能始终是一个考虑因素,其中大量纯粹是为了在根本上更快的线路上重写关键的承载器(尽管没有使用任何非标准编译器功能来这样做)。看看每个编译器对这个掉头的反应会很有趣,
这是最新的两个编译器版本 rev.301 现在熟悉的时序矩阵:
coan - rev.301 结果
| -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|
这里的故事仅与 GCC-4.8.1 和 Clang-3.3 略有不同。GCC 的表现要好一些。Clang的情况要差一些。噪音可以很好地解释这一点。Clang 仍然遥遥领先,-O2
在-O3
大多数应用程序中并不重要,但对很多应用程序却很重要。
这是 rev 的矩阵。619.
coan - 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 构建中从优化中获得比 GCC 更多的影响的能力在-O3
619 构建中消失了。编译器都没有从-O3
.
我对这种命运的逆转感到非常惊讶,我怀疑我可能不小心对 clang 3.4 本身进行了缓慢的构建(因为我是从源代码构建的)。所以我用发行版的 Clang 3.3 重新运行了 619 测试。结果实际上与 3.4 相同。
所以关于对掉头的反应:在这里的数字上,当我没有给它任何帮助时,Clang 在我的 C++ 代码中以极快的速度做的比 GCC 好得多。当我下定决心提供帮助时,GCC 比 Clang 做得更好。
我没有将这种观察提升为原则,但我吸取了“哪个编译器生成更好的二进制文件?”的教训。是一个问题,即使您指定了与答案相关的测试套件,仍然不是只为二进制文件计时的明确问题。
你更好的二进制文件是最快的二进制文件,还是最能弥补廉价代码的?或者最好地补偿那些优先考虑可维护性和重用而不是速度的昂贵的 代码?这取决于您生成二进制文件的动机的性质和相对权重,以及您这样做的约束条件。
无论如何,如果您非常关心构建“最佳”二进制文件,那么您最好继续检查编译器的连续迭代如何在代码的连续迭代中实现您的“最佳”想法。
Phoronix 对此做了一些基准测试,但它是几个月前 Clang/LLVM 的快照版本。结果是事情或多或少是一种推动。在所有情况下,GCC 和 Clang 都不是绝对更好。
由于您将使用最新的 Clang,它可能不太相关。话又说回来,显然,GCC 4.6将对Core 2和Core i7进行一些重大优化。
我认为 Clang 更快的编译速度对于原始开发人员来说会更好,然后当您将代码推向世界时,Linux 发行版、BSD等最终用户将使用 GCC 来获得更快的二进制文件。
Clang 编译代码更快的事实可能不如生成的二进制文件的速度重要。但是,这里有一系列基准。
就生成的二进制文件的速度而言,GCC 4.8 和 Clang 3.3 之间的总体差异非常小。在大多数情况下,两个编译器生成的代码执行相似。这两个编译器都没有支配另一个。
基准测试表明 GCC 和 Clang 之间存在显着的性能差距是巧合。
程序性能受编译器选择的影响。如果开发人员或一组开发人员专门使用 GCC,则可以预期使用 GCC 的程序运行速度比使用 Clang 稍快,反之亦然。
从开发人员的角度来看,GCC 4.8+ 和 Clang 3.3 之间的一个显着区别是 GCC 具有-Og
命令行选项。此选项启用不干扰调试的优化,例如,始终可以获得准确的堆栈跟踪。Clang 中缺少此选项使得 clang 更难用作某些开发人员的优化编译器。
我在 GCC 5.2.1 和 Clang 3.6.2 上注意到的一个特殊区别是,如果您有一个关键循环,例如:
for (;;) {
if (!visited) {
....
}
node++;
if (!*node)
break;
}
-O3
然后 GCC 将在使用or编译时-O2
,推测性地展开循环八次。Clang 根本不会展开它。通过反复试验,我发现在我的程序数据的具体情况下,正确的展开量是 5,因此 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++ 代码时,我没有对任何一个编译器的性能做出任何声明。
在Ubuntu 15.10 (Wily Werewolf)、x86.64 和AMD Phenom II X6 1090T 处理器上。
基本上,答案是:视情况而定。有许多基准针对不同类型的应用程序。
我的应用程序基准是:GCC > ICC > Clang。
很少有 I/O,但有很多 CPU 浮点和数据结构操作。
编译标志是-Wall -g -DNDEBUG -O3。
https://github.com/zhangyafeikimi/ml-pack/blob/master/gbdt/profile/benchmark