当前位置: 首页 > 科技观察

C++语言库(ETL)的GCC和Clang编译器基准测试报告

时间:2023-03-20 01:35:02 科技观察

我已经有一段时间没有使用C++代码对不同的编译器进行基准测试了。由于我最近发布了我的ETL项目的1.1版(一个带有表达式模板的优化矩阵/向量计算库),我决定使用它作为我的基准测试的基础版本。它是一个包含大量模板的C++14库。我想编译完整的测试套件(124个测试用例)。这是直接在最新版本(1.1)的代码上完成的。我将在调试模式下编译一次,在release_debug(发布+调试符号和断言)下编译一次,并记录每个编译器的执行时间。测试将使用支持ETL中每个选项的配置进行编译,以计算最大编译时间。每个编译使用四个线程(make-j4)。我还做了一些基准测试,以查看每个编译器生成的代码之间的运行时性能差异。基准测试在发布模式下编译,并记录它们的编译时间。我将测试以下编译器:GCC-4.9.4GCC-5.4.0GCC-6.3.0GCC-7.1.0clang-3.9.1clang-4.0.1zapcc-1.0(商业版,基于clang-5.0masterbranch)所有这些都是使用Portage(Gentoo包管理器)直接安装的,除了从源代码安装的clang-4.0.1和没有Gentoo包的zapcc。由于Gentoo上的clang包不支持多处理,我不得不从源代码安装一个版本,从包管理器安装另一个版本。这就是为什么我测试的clang版本更少,更实用。为了实现这些测试的目标,所有编译器都使用了完全相同的选项。一般来说,我在clang上使用的选项比GCC多(主要是因为clang上的矢量化选项更严格)。这可能不会为每个编译器带来最佳性能,但允许使用默认优化级别比较输出。下面是主要用到的选项:调试模式下:-g发布+调试模式下:-g-O2发布模式下:-g-O3-DNDEBUG-fomit-frame-pointer每种情况都会启用很多警告,ETL选项是相同。所有测试结果均在运行IntelCorei7-2600(SandyBridge...)@3.4GHz、4核8线程、12GRAM和SSD的Gentoo机器上收集。我尽力将基准数据与干扰因素隔离开来,我的基准代码相当健壮,但有些结果可能并不完全准确。此外,一些基准测试正在使用多线程,这会增加一些噪音和不可预测性。当我不确定测试结果时,我会多次运行基准测试来确认这一点,总的来说我对结果很有信心。编译时间先从编译器自身的性能结果说起:注意:在Release_Debug和Benchmark中,我只用了三个线程做zapcc,因为12Go的内存不够用四个线程。不同的编译器之间有一些非常重要的区别。总的来说,clang-4.0.1是迄今为止调试模式下最快的免费编译器。但是,当编译测试代码并添加优化时,clang落后了。令人印象深刻的是,clang-4.0.1在调试和发布模式下都比clang-3.9.1快得多。在这一点上,clang团队做得很好!通过这些优化,clang-4.0.1在发布模式下几乎与gcc-7.1持平。对于GCC,优化的成本似乎一直在显着上升。然而,GCC7.1似乎使优化和标准编译速度更快。如果我们考虑zapcc,它是调试模式下最快的编译器,但它的速度比发布模式下的几个gcc版本慢。总的来说,clang-4.0.1的性能给我留下了深刻的印象,它看起来真的很快!在不久的将来,我肯定会用这个新版本做更多的测试。令人欣慰的是,g++-7.1确实比gcc-6.3编译得更快。不过为了优化,最快的gcc版本还是gcc-4.9.4,已经是老版本了,对C++标准的支持很低。运行时性能现在让我们看看生成代码的质量。对于某些基准测试,我包含了该算法的两个版本。std是最简单的算法(原始版本),vec是手动矢量化和优化的实现。所有测试均在单精度浮点数上完成。点积运行的第一个基准是计算两个向量之间的点积。我们先看看原版的性能:不同的编译器差异不是很大。基于clang的编译器似乎是生成最快代码的编译器。有趣的是,gcc-6.3似乎在大数据量容器中性能受到很大影响,但这在gcc-7.1中已得到解决。如果我们查看优化版本的结果,差异会更小。同样,基于clang的编译器生成了最快的可执行文件,但gcc紧随其后,gcc-6.3除外,我们仍然看到与以前相同的性能回归。LogisticSigmoid下一个测试是检查sigmoid操作的性能。在这种情况下,库的评估器将尝试使用并行化和矢量化进行计算。让我们看看不同编译器的成本如何:有趣的是,我们可以看到gcc-7.1对于少量数据最快,而clang-4.0最适合生成大量数据的代码。但是,除了最大的向量大小之外,差异不是很明显。显然zapcc(或clang-5.0)中存在回归,因为它比clang-4.0慢,但与clang-3.9的速度大致相同。Y=Alpha*X+Y(axpy)第三个基准是著名的axpy(y=alpha*x+y)。这完全由库中的表达式模板决定,没有使用特定的算法。让我们看一下结果:一旦矢量化和并行化,即使对于最大的矢量,这也是一个非常快的操作。按照这个速度,观察到的一些差异可能不是很重要。同样,基于clang的版本是这段代码中最快的,但差异仍然很小。gcc-7.1似乎也有一点退化,但这也相当小。矩阵间乘法(GEMM)下一个基准测试矩阵-矩阵乘法的性能,这是一种在BLAS命名法中称为GEMM的运算。在这种情况下,我们测试了原始和优化的矢量化实现。为了节省一些水平空间,我将表格分成两部分。这个时候不同编译器的性能差异就非常大了。clang编译器现在遥遥领先,clang-4.0是其中最快的(也有很好的提升)。事实上,clang-4.0.1生成的代码平均比最好的GCC编译器生成的代码快两倍。非常有趣的是,我们可以看到从GCC-5.4开始出现巨大的性能退化,而这种退化仍然存在于GCC-7.1中。事实上,测试版中最好的GCC版本仍然是GCC-4.9.4。Clang在编译GEMM代码方面做得非常好。至于优化版本,这两个系列是相反的。确实,GCC在这方面比clang做得更好,虽然差距没有以前那么大了,但还是值得一提的。我们仍然可以观察到GCC版本中的一个小回归,因为4.9版本仍然是最快的。至于clang版本,貌似clang-5.0(用在zapcc中)在本例中有不少性能提升。在这个矩阵乘法示例中,令人印象深刻,优化代码和未优化代码之间的性能差异巨大。而且,令人印象深刻的是,每种类型的编译器都有其优势,clang似乎更擅长处理未优化的代码,而GCC更擅长处理矢量化代码。