文档结构  
翻译进度:已翻译     翻译赏金:10 元 (?)    ¥ 我要打赏

自从我做了关于使用C++代码的各种不同编译器基准已经有一段时间了。由于我最近 发布了自己的ETL项目的1.1版本(带有各种表达式模板的优化矩阵/向量计算库),所以我决定使用它作为自己基准的基础。这是一个带有许多模板的C++14库。我要编写完整的测试用例(124个测试例)。这是直接依据最后一个版本(1.1)代码进行的。我将要以调试模式编译一次,一次以release_debug模式(发布外加各种调试符号和断言),并为每一个编译器记录下时间。为了考虑最大编译时间各种测试是通过支持ETL中的每一个选项来编译的。每个编译都是使用四个线程来进行的(进行-j4)。我还将测试一些基准去查看每个编译器生成的代码在运行时性能上的差异。该基准将按发布模式下编译,并且编译时间也被记录下来。

第 1 段(可获 2.24 积分)

我将会测试下列编译器:

  • GCC-4.9.4
  • GCC-5.4.0
  • GCC-6.3.0
  • GCC-7.1.0
  • clang-3.9.1
  • clang-4.0.1
  • zapcc-1.0 (商业版,基于 clang-5.0 分支)

除了 clang-4.0.1 与zapcc,所有的编译器都是使用Portage(Gentoo包管理器)直接安装。clang-4.0.1 由源码安装,而zapcc则没有对应的Gentoo包。因为 Gentoo 上的 clang 包并不支持多版本,我不得不由安码安装一个版本,而其他编译器由包管理器安装。这也正是我测试的 clang 版本较少的原因,只是不太实用。

第 2 段(可获 1.3 积分)

这些测试的目的是为了在所有编译器中都使用完全相同的选项。 通常,对于clang,我使用不同的选项,而不是GCC (主要是在clang上更有主动性的矢量化选项). 这可能不会为每个编译器带来最佳性能,但是可以将结果与默认优化级别进行比较。以下主要的使用选项:

  • In debug mode: -g
  • In release_debug mode: -g -O2
  • In release mode: -g -O3 -DNDEBUG -fomit-frame-pointer

在每种情况下,都会启用大量警告,并且和ETL选项是相同的。

在一部Gentoo 机上汇总了所有的结果,该机运行英特尔酷睿 i7-2600系统(代系列…)——拥有3.4GHz,4核,8线程,12Go内存和SSD。我尽可能地从各种干扰中隔离自己的基准,并且我的基准代码是相当健全的,却很可能有些结果并不完全准确。此外,一些基准测试使用的是多线程,这可能会增加一些噪声和不可预测性。当我对结果没有十足把握时,我多次运行基准测试来确认它们,总体上我对各种结果信心满满。

第 4 段(可获 1.3 积分)

编译时间

让我们从编译器自身的性能结果开始:Image title

注: 我只使用了三个与zapcc有关的线程,因为对四个线程来说,12Go 的RAM内存是不够。

不同编译器之间有一些非常显着的差异。 总体来说,clang-4.0.1是迄今为止调试模式最快的免费编译器。 然而当测试编译优化, clang 落后了。在调试模式和发布模式中,clang - 4.0.1管理的速度比clang-3.9.1要快得多。在这里,clang团队真的很棒! 通过这些优化,clang-4.0.1在发布模式下几乎与gcc-7.1相同。对于GCC来说,优化的成本似乎已经大大提高了。然而,GCC 7.1似乎已经使得优化更快,标准编译也更快。如果我们考虑到zapcc,它是调试模式下最快的编译器,但在发布模式下它比几个gcc版本慢。

第 5 段(可获 2.28 积分)

整体而言, clang-4.0.1 的性能令我印象深刻,看起来非常的快。在不久的将来我一定会对这个新版本的编译器进行更多的测试。同时也很高兴地看到 g++-7.1 的构建速度要快于 gcc-6.3。然而对于优化最快的 gcc 版本依然是 gcc-4.9.4 ,这已经是一个很旧的分支,并且具有较少的 C++ 标准支持。

运行时性能

现在让我们看一下生成代码的质量。对于某些测试基准,我包含了两个算法版本。 std 是最简单的算法(简单版本),而 vec 是手动向量化与优化实现。所有的测试都是在单精度浮点数上完成的。

第 6 段(可获 1.63 积分)

点积

运行的每一个测试基准是计算两个向量之间的点积。让我们首先看一下简单版本:

Image title

不同的编译器之间的区别并不是很大。基于 clang 的编译器看起来是生成最快代码的编译器。有趣的是,gcc-6.3 中对于大容器似乎有一些退货,但是在 gcc-7.1 中已经得到了修复。

Image title

如果我们看一下优化版本,不同的编译器之间的区别更为微小。再一次看到,基于 clang 的编译器是生成最快可执行代码的编译器,但是紧随其后的是 gcc 编译器,除了 gcc-6.3 ,因为可以看到与之前相同的退化问题。

第 7 段(可获 1.4 积分)

Logistic Sigmoid

接下来的测试是检测 sigmoid 操作的性能。在该测试中,库的评估器将会尝试使用并行与向量化进行计算。让我们看一下不同编译器的结果:

Image title

有趣的是,我们可以看到 gcc-7.1 对小向量是最快的,而 clang-4.0 最适合于为大向量生成代码。然而,除了最大的向量尺寸,编译器之间的区别并不是特别明显。很明显,在 zapcc (或  clang-5.0)中存在退化,因为在相同级别上,他要慢于 clang-4.0 与 clang-3.9 。

第 8 段(可获 1.28 积分)

Y = Alpha * X + Y (axpy)

第三个测试基准是著名的 axpy (y = alpha * x + y)。这完全是通过库中的表达式模型来解决的,而没有使用特定的算法。让我们看一下结果:

Image title

一旦实现向量化与并行化,即使是在最大的向量上,这也是非常快的操作。在这个速度级别上,可观察到的区别并不是很明显。再一次看到,在这个测试代码上,基于 clang 的编译器依然是最快的,但是优势很微弱。似乎在 gcc-7.1 中存在较小的退化,但是非常小。

矩阵与矩阵乘法 (GEMM)

第 9 段(可获 1.23 积分)

下一个测试基准是测试矩阵与矩阵相乘,在BLAS命名中被称为 GEMM 操作。在这个测试中,我们同是时测试简单与优化的向量化版本。为了节省水平空间,我将表格分为两部分。

Image title

这一次,不同编译器之间的区别非常明显。clang 编译器以极大的优势领先其他编译器, clang-4.0 是其中最快的(另一个较大的优势)。确实, clang-4.0.1 生成的代码比最好的 GCC 编译器生成的代码平均快2倍。同时非常有趣的是,我们可以看到由 GCC-5.4 开始有一个较大的退化,且在  is producing code that is, on average, about twice faster than the code generated by the best GCC compiler. Very interestingly as well, we can see a huge regression starting from GCC-5.4 and that is still here in GCC-7.1. Indeed, the best GCC version, in the tested versions, is again GCC-4.9.4. Clang is really doing an excellent job of compiling the GEMM code.

第 10 段(可获 1.89 积分)

Image title

对于优化版本,似乎分为两大阵营。确实,在这个测试中 GCC 要优于 clang ,尽管优势并不像之前那样大,但依然很明显。我们依然可以看到 GCC 版本中的退化,因为 4.9 版本再一次是最快的。对于 clang 版本,似乎 clang-5.0 (用在zapcc)在这个测试中有一些性能改进。

对于矩阵相乘测试,令人印象深刻的是,非优化代码中的区别是如此明显。同时令人印象深刻的是,每个编译器家族有其自己的长处, clang 看起来更适合处. And it's also impressive that each family of compilers has its own strength, clang being seemingly much better at handling unoptimized code while GCC is better at handling vectorized code.

第 11 段(可获 1.59 积分)

卷积(2D)

我设计的最后一个测试是在2D图像上的卷积测试。测试代码与 GEMM 代码非常相似,但由于缓存的局部性,优化更为复杂。

Image title

在这个测试中,我们可以观察到与 GEMM 相同的结果。基于 clang 的编译器生成的代码运行速度明显快于 GCC 版本。而且,我们同时可以观察到由 GCC-5.4 开始的较大退化。

Image title

这一次, clang 得到了最优的结果。确实,clang 所生成的可执行代码的运行速度明显快于 GCC 版本生成的代码,除了 GCC-7.1 会生成类似的结果。看起来其他的 GCC 版本会落后于 clang。 看起来仅于 GEMM, clang 在处理优化代码时遇到了麻烦。

第 12 段(可获 1.7 积分)

结论

看起来最近 clang 在编译时间上做了大量优化。确实,clang-4.0.1 的编译速度要快于 clang-3.9 。尽管 GCC-7.1 要快于 GCC-6.3,所有的 GCC 版本要慢于 GCC-4.9,GCC-4.9 是编译优化代码最快的 GCC 版本。GCC-7.1 是在 Debug 模式下代码编译最快的 GCC 版本。

在某些情况下,不同的编译器所生成的代码之间几乎没有区别。然而,在更为复杂的算法中,例如矩阵相乘或是二维卷积,其间的区别非常明显。在我的测试中, clang 已经显示出更适合于编译非优化代码。然而,特别在 GEMM 测试中,在处理手动优化的代码时似乎弱于 GCC 。我将会研究该测试,并且修改代码使得 clang 有更好的测试结果。

第 13 段(可获 1.91 积分)

对于我来说,很奇怪的是,GCC的回归从GCC - 5.4开始的,在GCC 7.1中仍然没有得到修复。 我正在考虑放弃对GCC-4.9的支持,以便全力支持C14的支持,但现在我可能需要重新考虑我的立场。然而,看到GCC在处理优化代码方面通常是最好的(特别是对于GEMM),我可以做过渡,因为在大多数情况下,优化的代码将被使用。

至于zapcc,虽然它仍然是调试模式下最快的编译器,与新clang-4.0.1的速度,其幅度很小。此外,在优化构建上,它不如GCC快。 如果你使用Clang,可以访问zapcc,节省时间上,它仍然是相当好的选择。

第 14 段(可获 1.76 积分)

总体而言,clang-4.0.1和GCC-7.1我很满意,最近的版本我一直在测试。他们似乎做的很不错。我一定会和他们一起进行一些测试,并尝试调整代码。我还在考虑是否会支持一些较旧的编译器。

第 15 段(可获 0.73 积分)

文章评论