Description
不到一周前,我们终于默认启用了 Linux 和 macOS 上 Debug 构建的 x86_64 后端。今天,我们对其进行了重大性能改进:我们进一步并行化了编译器流水线!
这些好处不会影响 LLVM 后端,因为它使用了更多的共享状态;事实上,它仍然仅限于一个线程,而在此更改之前,其他每个后端都能够使用两个线程。但是对于自托管后端,机器代码生成本质上是一个独立的任务,因此我们可以将其与所有其他任务并行运行,甚至可以并行运行多个代码生成作业。然后,生成的机器代码最终在链接器线程上粘合在一起。这意味着我们最终会得到一个线程执行语义分析,任意多个线程执行代码生成,以及一个线程执行链接。并行化这个阶段特别有益,因为 x86_64 的指令选择非常复杂,原因是该架构具有大量的扩展和指令。
这项工作最终形成了一个由我和 Jacob 创建的 PR#24124,几天前已合并。这是一项相当大的工作,因为需要重新设计编译器流水线的许多内部细节,以便将机器代码生成与链接器完全隔离。但最终一切都物有所值,因为性能得到了提升!使用自托管的 x86_64 后端,我们看到编译 Zig 项目的实际时间改进了 5% 到 50% 不等。例如,Andrew 报告说,他能够以 10 秒或更短的时间构建 Zig 编译器本身(不包括链接 LLVM,这会增加几秒钟的时间):
Benchmark 1 (32 runs): [... long command to build compiler with old compiler ...]
measurement mean ± σ min … max outliers delta
wall_time 13.8s ± 71.4ms 13.7s … 13.8s 0 ( 0%) 0%
peak_rss 1.08GB ± 18.3MB 1.06GB … 1.10GB 0 ( 0%) 0%
cpu_cycles 109G ± 71.2M 109G … 109G 0 ( 0%) 0%
instructions 240G ± 48.3M 240G … 240G 0 ( 0%) 0%
cache_references 6.42G ± 7.31M 6.41G … 6.42G 0 ( 0%) 0%
cache_misses 450M ± 1.02M 449G … 451G 0 ( 0%) 0%
branch_misses 422M ± 783K 421M … 423M 0 ( 0%) 0%
Benchmark 2 (34 runs): [... long command to build compiler with new compiler ...]
measurement mean ± σ min … max outliers delta
wall_time 10.00s ± 32.2ms 9.96s … 10.0s 0 ( 0%) ⚡- 27.4% ± 0.9%
peak_rss 1.35GB ± 18.6MB 1.34GB … 1.37GB 0 ( 0%) 💩+ 25.7% ± 3.9%
cpu_cycles 95.1G ± 371M 94.8G … 95.5G 0 ( 0%) ⚡- 12.8% ± 0.6%
instructions 191G ± 7.30M 191G … 191G 0 ( 0%) ⚡- 20.6% ± 0.0%
cache_references 5.93G ± 33.3M 5.90G … 5.97G 0 ( 0%) ⚡- 7.5% ± 0.9%
cache_misses 417M ± 4.55M 412M … 421M 0 ( 0%) ⚡- 7.2% ± 1.7%
branch_misses 391M ± 549K 391M … 392M 0 ( 0%) ⚡- 7.3% ± 0.4%
作为另一个数据点,我测量了构建一个简单的“Hello World”所用时间缩短了 30%:
Benchmark 1 (15 runs): /home/mlugg/zig/old-master/build/stage3/bin/zig build-exe hello.zig
measurement mean ± σ min … max outliers delta
wall_time 355ms ± 4.04ms 349ms … 361ms 0 ( 0%) 0%
peak_rss 138MB ± 359KB 138MB … 139MB 0 ( 0%) 0%
cpu_cycles 1.61G ± 16.4M 1.59G … 1.65G 0 ( 0%) 0%
instructions 3.20G ± 57.8K 3.20G … 3.20G 0 ( 0%) 0%
cache_references 113M ± 450K 112M … 113M 0 ( 0%) 0%
cache_misses 10.5M ± 122K 10.4M … 10.8M 0 ( 0%) 0%
branch_misses 9.73M ± 39.2K 9.67M … 9.79M 0 ( 0%) 0%
Benchmark 2 (21 runs): /home/mlugg/zig/master/build/stage3/bin/zig build-exe hello.zig
measurement mean ± σ min … max outliers delta
wall_time 244ms ± 4.35ms 236ms … 257ms 1 ( 5%) ⚡- 31.5% ± 0.8%
peak_rss 148MB ± 909KB 146MB … 149MB 2 (10%) 💩+ 7.3% ± 0.4%
cpu_cycles 1.47G ± 12.5M 1.45G … 1.49G 0 ( 0%) ⚡- 8.7% ± 0.6%
instructions 2.50G ± 169K 2.50G … 2.50G 1 ( 5%) ⚡- 22.1% ± 0.0%
cache_references 106M ± 855K 105M … 108M 1 ( 5%) ⚡- 5.6% ± 0.4%
cache_misses 9.67M ± 145K 9.35M … 10.0M 2 (10%) ⚡- 8.3% ± 0.9%
branch_misses 9.23M ± 78.5K 9.09M … 9.39M 0 ( 0%) ⚡- 5.1% ± 0.5%
顺便说一句,我非常喜欢一些好的 std.Progress 输出,所以我忍不住想说我现在多么喜欢看着编译器,看到它所做的所有工作:
即使有这些数字,我们在编译器性能方面仍然远未完成。未来对我们的自托管链接器的改进,以及将函数发出到最终二进制文件中的代码的改进,可能有助于加速链接,而链接现在有时是编译速度的瓶颈(您实际上可以在上面的 asciinema 中看到这个瓶颈)。我们还希望提高我们发出的机器代码的质量,这不仅使 Debug 二进制文件执行得更好,而且(可能与直觉相反)应该进一步加快链接速度。我们关注的其他性能工作包括减少编译器在编译结束时(其“刷新”阶段)所做的工作量,以消除另一个很大的开销,以及(在更遥远的未来)并行化语义分析。
也许最重要的是,增量编译——这是 Zig 项目多年来的长期投资——在某些情况下已经非常接近默认启用,这将允许对小的更改在几毫秒内重建。顺便说一句,请记住,只要您能接受可能的编译器错误,您现在就可以尝试增量编译并开始获得它的好处!如果您想了解更多相关信息,请查看跟踪问题。
闲聊够了——我希望你们都和我们一样对这些改进感到兴奋。Zig 的编译速度是有史以来最好的,并且希望是未来最差的 ;)
加入我们
Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来: