铿锵VS海湾合作委员会 – 哪个产生更好的二进制?
我目前正在使用GCC,但是最近我发现了Clang,并且正在思考转换。 有一个决定因素 – 它产生的二进制文件的质量(速度,内存占用,可靠性) – 如果gcc -O3
能产生一个速度提高1%或内存减less1%的二进制文件,那么这是一个交易断路器。
Clang拥有比GCC更好的编译速度和更低的编译时内存占用,但是我真的对编译软件的基准/比较感兴趣 – 你能指点一些还是描述你的经验?
这里有一些最新的GCC 4.7.2和Clang 3.2 for C ++的最新发现。
更新:GCC 4.8.1 v铿锵3.3比较下面附加。
更新:GCC 4.8.2 v clang 3.4比较附加到该。
我使用GCC和Clang以及Microsoft的Windows编译器维护为Linux构build的OSS工具。 coan是一个C / C ++源代码文件和代码行的预处理器和分析器:它的recursion下降parsing和文件处理的计算概要专业。 开发分支(这些结果所属的分支)目前包含大约11K LOC,大约90个文件。 现在,它被编码为C ++,它具有丰富的多态性和模板,但是仍然被许多补丁所困扰,因为它在被攻击的时候并不那么遥远。C移动语义没有被明确地利用。 它是单线程的。 我没有认真努力去优化它,而“架构”仍然是如此之大。
在3.2之前,我只雇用Clang作为一个实验性的编译器,因为尽pipe它的编译速度和诊断速度出色,但它的C ++ 11标准支持在coan所执行的方面落后于当前的GCC版本。 随着3.2,这个差距已经结束。
我的Linuxtesting工具用于当前的coan开发,处理大约70K的源文件,混合使用单文件parsing器testing用例,压力testing需要消耗1000个文件,场景testing需要消耗<1K的文件。 除了报告testing结果外,线束还会累积并显示消耗的文件总数和在coan中消耗的运行时间(它只是将每个coan命令行传递给Linux time
命令并捕获并累计报告的数字)。 由于任何数量的可测量时间为0的testing都会加起来为0,但是这种testing的贡献可以忽略不计。 时间统计信息显示在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之间的testing工具性能,除了编译器之外,所有的东西都是一样的。 从Clang 3.2开始,我不再需要GCC将编译的代码片段和Clang替代scheme之间的预处理器区分。 我在每种情况下构build到相同的C ++库(GCC),并在同一个terminal会话中连续运行所有比较。
我发布版本的默认优化级别是-O2。 我也成功地testing了-O3版本。 我对每个configuration进行了3次背靠背testing,并对3个结果进行了平均,结果如下。 数据单元中的数字是coan可执行文件处理每个〜70Kinput文件(读取,parsing和写入输出和诊断)所消耗的平均微秒数。
| -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|
任何特定的应用程序都很可能具有不利于编译器优缺点的特性。 严格的基准testing采用不同的应用程序。 考虑到这一点,这些数据的值得注意的特点是:
- -O3优化对GCC有一定的不利影响
- -O3优化对Clang是非常有利的
- 在-O2的优化中,GCC比Clang要快得多
- 在-O3优化中,Clang比GCC重要得多。
这两个编译器进一步的有趣的比较在这些发现后不久就出现了。 Coan慷慨地使用智能指针,其中一个在文件处理中被大量使用。 这个特定的智能指针types是为了编译器的区分而在以前的版本中进行了typedef,如果configuration的编译器对它的使用有足够成熟的支持,那么就是std::unique_ptr<X>
,否则std::shared_ptr<X>
。 对std::unique_ptr
的偏见是愚蠢的,因为这些指针实际上是在四处转移,但std::unique_ptr
看起来像是替代std::auto_ptr
的fitter选项,当时C ++ 11变体对我来说是新颖的。
在实验性的构build过程中,为了衡量Clang 3.2对这个和类似的区别的持续需求,当我打算构buildstd::unique_ptr<X>
时候,我无意中构build了std::shared_ptr<X>
std::unique_ptr<X>
,并且惊讶地发现由此产生的可执行文件,默认-O2优化,是我见过的最快,有时达到184毫秒。 每个input文件。 随着源代码的这一改变,相应的结果是这些;
| -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 |
这里需要注意的是:
- 编译器现在都不能从-O3优化中获益。
- 在每个优化级别上,Clang击败GCC同样重要。
- GCC的性能只受到智能指针types变化的轻微影响。
- Clang的-O2性能受到智能指针types变化的重要影响。
在智能指针types更改之前和之后,Clang能够在-O3优化中构build一个速度更快的coan可执行文件,并且当指针types是最好的一个时,它可以在-O2和-O3上构build同样更快的可执行文件std::shared_ptr<X>
– 作业。
一个显而易见的问题是,我无法评论这个问题, 为什么 Clang应该能够在我的应用程序中find一个25%-O2的加速,当一个大量使用的智能指针types从独特变为共享时,而GCC是无所谓的到同样的改变。 我也不知道是否应该为Clang的-O2优化对我的智能指针select的智慧具有如此巨大的敏感度这样的发现而欢呼或发出嘘声。
更新:GCC 4.8.1 v铛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个文件的平均时间更长,这并不能反映最新的编译器的性能。 这是由于testing应用程序的后期开发分支同时付出了很多parsing复杂性,并为此付出了代价。 只有比例是显着的。
现在的注意点并不是新奇的:
- GCC对-O3优化无动于衷
- 铿锵从-O3优化中获益非常less
- 在每一个优化级别上,铿锵的GCC都有着同样重要的优势。
将这些结果与海湾合作委员会4.7.2和3.2的结果进行比较,发现海湾合作委员会在每个优化级别已经夺回了约四分之一的铿锵gem。 但是由于testing应用程序已经大量开发,与此同时,人们不能自信地将其归结为GCC代码生成的追赶。 (这一次,我注意到应用程序的快照,从中获得的时间,可以再次使用它。)
更新:GCC 4.8.2 v铛3.4
我完成了GCC 4.8.1 v Clang 3.3的更新,说我会坚持使用同样的coan snaphot进行更新。 但是我决定对这个快照(第301版) 和我通过testing套件的最新开发快照(版本619)进行testing。 这使得结果有一点经度,我有另一个动机:
我原来的post指出,我已经不遗余力地优化科恩的速度。 rev的情况仍然如此。 301.但是,当我将时间设备安装到可安装testing工具之后,每次运行testing套件时,最新变化对性能的影响都直接影响了我。 我发现这个数字通常是惊人的大,而且这种趋势比我认为function上的增益更为负面。
由rev。 从这里第一次发布以来,testing套件中每个input文件的平均处理时间已经翻了一番多。 在那个时候,我对自己十年不打扰绩效的政策进行了调整。 在最多619次的修改中,性能始终是一个考虑因素,其中很大一部分纯粹是从根本上更快地重写关键负载(尽pipe没有使用任何非标准编译器function)。 看到每个编译器对这个掉头的反应是很有趣的,
以下是目前熟悉的最新两个编译器build.301的时序matrix:
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。 海湾合作委员会的展示是一个更好的小事。 铿锵的是一个小事情。 噪音可以解释这一点。 铿锵仍然提出了-O2
和-O3
利润率,这在大多数应用中并不重要,但重要的是不less。
这里是rev的matrix。 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的数字并排放在一起,有几点说出来。
-
我的目标是编写更快的代码,这两个编译器都强调了我的努力。 但:
-
海湾合作委员会比铿锵慷慨地偿还这些努力。 在
-O2
优化中,Clang的619构build比其301构build速度快46%:--O3
Clang的改进是31%。 好,但在每个优化级别上,GCC的619版本比其301版本快两倍多。 -
海湾合作委员会更多地扭转了铿锵的以前的优势。 在每个优化级别上,GCC现在比Clang高出17%。
-
619构build中,Clang在301构build中获得比
-O3
优化更多的优势的能力已经消失了。 编译器不会从-O3
获得有意义的结果。
我对这个命运的逆转感到十分惊讶,我怀疑自己可能会意外地让一个叮当3.4本身(我从源头上build立起来的)瘫痪。 所以我用我的发行版Clang 3.3重新运行了619testing。 结果几乎与3.4相同。
所以关于掉头的反应:关于这里的数字,Clang做得比GCC好得多,当我不给它任何帮助的时候,我的C ++代码的速度已经很快了。 当我放下心思去帮忙的时候,GCC做得比Clang好得多。
我没有把这个观察提升到一个原则,但是我吸取了“哪个编译器产生更好的二进制文件?”的教训。 是一个问题,即使你指定了答案应该是相对的testing套件,仍然不是一个明确的问题,只是计算二进制文件。
是你的更好的二进制最快的二进制文件,还是最好的补偿廉价的代码? 还是最好的补偿昂贵的代码,优先考虑可维护性和重用速度? 这取决于你产生二元的动机的性质和相对权重,以及你所做的约束条件。
无论如何,如果你深深地关心构build“最好的”二进制文件,那么你最好继续检查编译器的连续迭代如何在你的代码的连续迭代中提供“最好”的想法。
Phoronix在这方面做了一些基准testing ,但是它是几个月前Clang / LLVM的快照版本。 结果是事情或多或less是推动; 在所有情况下,GCC和Clang都不是最好的。
既然你会使用最新的铿锵,也许有点不那么相关。 再说一次,GCC 4.6显然对Core 2和i7有一些主要的优化 。
我认为Clang的编译速度对于原始开发人员来说会更好,然后当你将代码推送到世界的时候,Linux发行版/ BSD /等等。 最终用户将使用GCC来获得更快的二进制文件。
Clang更快地编译代码的事实可能不像生成的二进制文件的速度那么重要。 不过,这是一系列的基准 。
在GCC 4.8和3.3版本之间,所得到的二进制文件的速度差别很小。 在大多数情况下,两个编译器生成的代码执行类似。 这两个编译器都不支配另一个编译器。
基准testing表明GCC和叮当之间有显着的性能差距是巧合的。
程序性能受编译器select的影响。 如果一个开发者或者一组开发者在使用GCC,那么使用GCC的时候程序的运行速度要比使用GCC的时候要快一些,反之亦然。
从开发人员的angular度来看,GCC 4.8+和3.3的显着区别在于GCC有-Og
命令行选项。 这个选项可以启用不会干扰debugging的优化,所以例如始终可以得到精确的堆栈跟踪。 在clang中没有这个选项使得clang更难用作一些开发人员的优化编译器。
唯一的方法来确定这是尝试它。 FWIW我已经看到使用苹果公司的LLVM gcc 4.2和普通的gcc 4.2(对于x86-64代码有相当多的SSE)相比有一些非常好的改进,但是对于不同的代码库来说则是YMMV。 假设你正在使用x86 / x86-64,而且你真的关心最后几个百分比,那么你也应该尝试使用英特尔的ICC,因为这通常可以击败gcc – 你可以从intel.com获得30天的评估许可并尝试它。
我在gcc 5.2.1和clang 3.6.2上注意到一个特殊的差异,就是如果你有一个如下的关键循环:
for (;;) { if (!visited) { .... } node++; if (!*node) break; }
然后gcc在用-O3
或-O2
编译时会推测8次循环。 铿锵不会展开它。 通过试验和错误,我发现在我的程序数据的具体情况下,正确的展开数量是五个,所以gcc过冲和铿锵声低于。 然而,超弹性对性能的影响更大,所以gcc在这里performance的更差。
我不知道展开的差异是一个大趋势,还是只是特定于我的场景的东西。
前段时间,我写了几个垃圾收集器 ,在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,x86.64和AMD Phenom(tm)II X6 1090T处理器上。
基本上,答案是:这取决于。 有许多关于不同应用的基准。
我的应用程序的基准是:gcc> icc>铛。
有很less见的IO,但是很多CPU浮点运算和数据结构操作。
编译标志是-Wall -g -DNDEBUG -O3。
https://github.com/zhangyafeikimi/ml-pack/blob/master/gbdt/profile/benchmark