Clang 2.7如何在2021年成立?

2021-01-31 17:27:27

最近有一个朋友了解了Proebsting的法律,并立即向我提及。我知道法律的存在,但我从未真正问过自己-我相信吗?

也就是说,如果每18年升级一次编译器,则平均而言,您期望代码在相同硬件上的性能提高一倍。

在我看来,我可以尝试做一个实验。我可以使用一个现代的编译器,并比较生成的代码的性能-也许还有其他一些指标-与20岁的代码相比。

至少这是我最初的意图;但是,我一直很想做另一个实验,以弄清楚LLVM在过去几年中是如何变化的。为了将这两者结合起来,我想得到一个旧版本的LLVM并在一个现代版本中进行测试。

为了使该实验更加有趣,我将测试LLVM 1.0-不幸的是,由于缺少32位系统标头,它仅带有32位Linux二进制文件,使我无法完全工作,并且在编译源文件之一时出现段错误。因此,我们将测试LLVM的两个版本:

LLVM 2.7。这是LLVM的第一个版本,其中包含可以编译C ++代码的Clang版本。

LLVM 11.这是我碰巧可用的LLVM的最新稳定版本。

LLVM 2.7于11年前于2010年4月发布。因此,根据Proebsting的定律,我们不会期望将速度提高2倍-仅为1.5倍。

我们将在编译时和运行时轴上比较这些编译器,如下所示:

使用https://github.com/zeux/meshoptimizer库的合并版本,我们将为每个编译器多次构建libmeshoptimizer.o,并带有和不带有优化(-O0至-O3),并注意建立时间。

使用生成的优化后的.o文件,我们将使用现代clang编译经过网格优化的演示程序,在Stanford Dragon Mesh上运行它,并比较各种算法的时序。

我们将单独编译演示程序的原因是,演示程序使用STL,而我不想查找与这些较早的编译器兼容的STL版本。

注意:我知道这不是分析法律的严格或科学方法;法律本身也有些面目,那么谁在乎呢?不要过多地阅读结果。

我已经从https://releases.llvm.org/下载了LLVM 2.7的二进制版本。 LLVM 11随Ubuntu 20一起提供。我正在Linux分区上使用WSL2运行所有程序,以确保性能数字代表真实的硬件。

每个编译器都用于以四种转换方式将所有meshoptimizer源(8.5 KLOC)作为单个转换单元进行构建,以简化构建过程:-O0,-Os -DNDEBUG,-O2 -DNDEBUG和-O3 -DNDEBUG。

基于此分析,我们可以看到调试编译吞吐量没有受到很大的影响-在10多年的开发时间中,clang + llvm的调试版本速度降低了15%,这不足为奇,也并不特别令人震惊。但是,释放模式明显较慢-O2 / O3的速度要慢2.2倍。

就输出大小而言,数字看起来很健康-O2 / O3的构建增加了约25-30%,但只要我们看到匹配的性能提高,它本身就不是问题-在OS中,大小很重要,二进制文件缩小了8%。

比较运行时的问题在于,尚不清楚我们需要比较哪些特定的构建,以及我们需要进行基准测试的代码。meshoptimizer附带了许多具有各种性能特征的算法。对所有这些进行分析将很有趣,但是由于本文并不承诺是科学的,因此我们将选择一些算法并在所有构建配置中对其进行度量,然后选择一种配置进行挖掘进一步纳入Proebsting法律。

为了获得基本的了解,我们仅选择三种算法-顶点缓存优化,简化和索引解压缩。稍后,我们将进一步研究其他算法的性能,但是最好能了解较小集合上版本之间的差异。

在这里开始出现的画面似乎相当严峻。我们看到优化构建中的速度提高了10%至15%,但Os中的索引解压缩似乎更像是一个异常值,在LLVM 11中,-Os内联启发式算法可能会在不同的优化级别上产生相同的代码。在未优化的版本中,我们还会看到速度提高了5%。

现在,重要的是,除了关于比较不是特别科学的免责声明之外,读者还应该了解一个额外的细节-Meshoptimizer中的所有算法均经过仔细优化。这不是普通的C ++代码-这是在各种探查器下研究并经过调整的代码,直到在保持合理简洁的前提下,才认为该性能值得。

从理论上讲,不太仔细优化的代码可能表现出不同的行为,或者此处选择的基准根本无法像其可能那样适合编译器优化-不同优化级别之间缺乏显着差异也很值得注意(尽管以前在学术研究中特别研究过O3,但这种模式的价值尚无定论)。

为了获得更完整的图像,我们现在看一下更多算法,并仅在O2构建中对它们进行比较。

我们将首先查看meshoptimizer库中一套更完整的算法;这并不是现有的每个算法,因为某些算法的性能特征与此处已经介绍的其他算法相比并没有很大区别。这也排除了将要单独提及的顶点减压。

总体而言,这里的图片与我们已经建立的图片并没有太大不同-LLVM 11似乎生成的代码在大多数基准测试中的速度提高了10-15%。在几个离群值中,性能提升更为可观,最高可达25%;在几个基准测试中,LLVM 11实际上生成始终较慢的代码,最高可达5%,这不是测量误差。

我使用-O3重新测试了异常值,得出以下结果,该差距虽然不大但仍然很大:

这些收益当然是受欢迎的,尽管不幸的是,它们似乎是以较慢的2倍编译速度为代价的。这使我回到"优化编译器的死亡"作者:David J. Bernstein,我不知道是否可以找到一个更幸福的中间立场,在这个中间立场,编译器将更多的优化决策控制权交给开发人员,并允许调整代码以达到在此处可以看到的收益。降低复杂度和编译性能成本。

之前介绍的所有算法都是标量,使用可移植的C ++实现。尽管其中的某些部分可以在理论上进行矢量化处理,但实际上,即使在-O3时,clang 11仍难以为大多数/全部生成有效的SIMD代码。

meshoptimizer确实有几种具有一流SIMD版本的算法,这些算法使用SSE / NEON / Wasm内部函数实现。使用codecbench比较了它们的性能,codecbench是meshoptimizer附带的实用程序,输出性能以GB / sec为单位-因此下表中的数字是相反的,越大越好。

所有过滤器都是典型的SIMD流内核-没有分支或复杂的数据依存关系。也许不足为奇的是,编译后的代码的性能差异并不是很大。顶点解码实际上要复杂得多-它包含函数调用,分支,标量和矢量指令的混合,并且通常对优化器而言更具挑战性。

值得注意的是,在此特定示例上,将-O3与LLVM 2.7一起使用可将性能从2.3 GB / s提高到2.7 GB / s,而对LLVM 11没有影响-使LLVM 11和LLVM之间的增量2.7回到〜10%范围。

毫无疑问,可以找到LLVM 2.7不能进行矢量化的循环的示例(由于没有自动矢量化器),而LLVM 11可以-不幸的是,我的经验即使是在上述过滤器之类的简化内核上也使我不得不保持对自动向量化器的高度不信任(在上述4个过滤器内核中,clang 11甚至不能对一个向量进行向量化,而gcc 10只能对&exp;#4进行向量化)。我要说的是,除非为程序员提供了更好的工具以使这些优化更可预测和更可靠,否则自动矢量化带来的任何收益都不能算是重大的。

与10年前的LLVM 2.7相比,LLVM 11进行优化的代码往往花费2倍的时间来编译代码,因此生成的代码运行速度提高了10-20%(在任一方向上都有异常值)。这可能是一条通用规则,某些特定于高度调整的代码,某些特定于Meshoptimizer算法。

如果不花一个多晚上的时间,就很难消除其原因。这篇文章绝对不假装是一项详尽的研究-只是关于有趣的2.7 c语在2021年的样子的有趣的小研究。毫无疑问,LLVM背后的惊人社区没有花了过去的十年时间-但如果您仍然相信足够聪明的优化编译器,那么也许是时候重新考虑可以依靠编译器使您的代码年复一年地更快的程度了,就像任何有前途的东西一样#39的法律可能应重新制定为:

重要的是要认识到,有很多因素共同决定了软件性能变化的速度-硬件之间的速度越来越快(是的,即使是在最近的10年中,尽管Herb Sutter是什么)。免费午餐结束了(会让您相信),编译器变得越来越好,软件开发实践经常失控,并且软件开发人员的专业知识与优化之间存在巨大差异,编译器的进步只是其中的一小部分,难题。也许大卫·伯恩斯坦毕竟是对的。