我是如何提升 Rust 编译器的速度?
Posted CSDN
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我是如何提升 Rust 编译器的速度?相关的知识,希望对你有一定的参考价值。
作者 | Nicholas Nethercote
译者 | 弯月,责编 | 伍杏玲
出品 | CSDN(ID:CSDNnews)
【CSDN 编者按】本文是Rust的核心研发人员,在本文中,他将分享一下再2019年,他在提升 Rust 编译器的速度上,做了哪些新的工作来优化它。
更快的Globals
libsyntax用一个全局数据结构Globals存储了3个表,分别存储了有关span(代码位置)、符号和数据清理(与宏扩展相关)的信息。访问这些表的代价很高,所以我找到了许多改进方法。
#59693(https://github.com/rust-lang/rust/pull/59693):AST中的每个元素都有一个span,描述了元素在原始源代码中的位置。
每个span由偏移、长度以及与宏扩展相关的值组成。这三个字段共占12个字节,考虑到它需要添加到AST的每个元素上,12个字节太多了,而且很多时候这三个字段并不需要这么多空间。
因此,编译器采用了只需4字节的压缩格式,而当长度超过4字节时则采用备用策略——将其存储到Globals中的哈希表中。而该PR将其改为了8个字节。这会稍稍增加内存的使用量,但上述备用策略的执行率可以从10-20%降低到不足1%,这可以加速许多工作负载,最大可以提升14%。
#61253(https://github.com/rust-lang/rust/pull/61253):有许多操作都需要访问数据清理信息,通常这些操作会调用两次甚至三次,因此会造成重复查找数据清理信息。该PR引入了组合操作,可以避免重复查找。这可以将packed-simd上的速度提高10%,对于普通工作负载效率可以提高3%。
#61484(https://github.com/rust-lang/rust/pull/61484):与上述#61253类似,只不过这个PR在许多基准测试中都赢得了提高2%的好成绩。
#60630(https://github.com/rust-lang/rust/pull/60630):编译器有一个驻留的字符串类型,名叫符号(symbol)。编译器对该类型的使用不太一致。因此,导致许多符号与普通字符串的比较需要逐个字符比较符号表中的字符串。
实际上,符号与符号的比较的开销很小,只需进行整数比较。该PR删除了符号与字符串的比较操作,强迫编译器更广泛地使用符号类型。(幸运的是,大多数引入的符号都使用了静态已知、预先驻留的字符串,因此没有额外的成本。)该PR在各种基准测试中赢得了高达1%的效率提升,而且符号的使用也更为一致。
#60815(https://github.com/rust-lang/rust/pull/60815):与上述#60630类似,该PR也在各项基准测试中赢得了高达1%的效率提升。
#60467、#60910、#61035、#60973:这些PR避免了一些不必要的符号,可以进一步将编译器的效率提高1%。
其他
以下各项提升没有统一的主题。
#57719(https://github.com/rust-lang/rust/pull/57719):该PR将一个常用的函数改成了内联函数,可以将一个工作负载的效率提高4%。
#58210(https://github.com/rust-lang/rust/pull/58210):该PR改变了一个常用的断言(仅在调试版本中有效),可以将一个工作负载的效率提高20%!
#58207(https://github.com/rust-lang/rust/pull/58207):上述我曾提到了字符串驻留。Rust编译器还会针对其他经常出现重复值的类型使用驻留,包括一种名叫LazyConst的类型。
然而,intern_lazy_const函数有很多Bug,而且没有真正执行驻留,它只是分配了一个新的LazyConst,而却不会检查之前是否遇到过!该PR修复了这个问题,大幅降低了内存使用率的峰值,而且页错误还减少了59%。
#59507(https://github.com/rust-lang/rust/pull/59507):该美化输出会针对每个缩进的空格调用write!,在某些工作负载上,缩进级别会超过100级。该PR能在绝大多数情况下将所有处理放到一个write!调用中。该PR可以在多个基准测试中体现出7%的提升。
#59626(https://github.com/rust-lang/rust/pull/59626):该PR更改了数据结构的预分配大小,以更好地满足实际需要,在某些工作负载上,可以将内存使用量的峰值减少20 MiB。
#61612(https://github.com/rust-lang/rust/pull/61612):该PR优化了解析器中的热路径,因为常量标志符会毫无目的地重复“这是不是一个关键字?”的测试,对于拥有大量常量的程序来说,该PR可以提升7%的性能。
分析改进
下面的改进涉及一些分析工具。
#59899(https://github.com/rust-lang/rust/pull/59899):为了让枚举变量从大到小列举,我修改了-Zprint-type-sizes的输出。该PR可以方便你看到超大的变量,特别是对于有许多变量的枚举。
#62110(https://github.com/rust-lang/rust/pull/62110):为了改进-Ztime-passes标志的输出,我删除了一些无用信息的输出,并添加了总编译时间的度量。
此外,我还改进了rustc-perf基准测试套件中的分析支持。首先,我添加了OProfile性能分析的支持。我承认至今我还没有发现显著的提升。在运行时,大约有一半的几率会出现错误,很让人失望。
其次,我添加了新版DHAT性能分析的支持。虽然这篇文章写自2019年,但值得一提的是,我于2018年对新DHAT的帮助做了一些改进,PR包括:#55167、#55346、#55383、#55384、#55501、#55525、#55556、#55574、#55604、#55777、#55558、#55745、#55778、#55905、#55906、#56268、#56090、#56269、#56336、#56369和#56737。
最后,我还写了一篇有关Rust性能所有基准的概要描:https://github.com/rust-lang-nursery/rustc-perf/blob/master/collector/benchmarks/README.md
流水线编译
上述改进(以及此前完成的所有改进)都属于微优化,也就是说,我使用分析数据来优化一小段代码。
但是,我们还应该考虑对Rust编译器速度进行更大规模的系统性改进。今年第二季度的时候,我曾与Alex Crichton在流水线编译项目上展开了合作,这一功能通过叠加依赖箱(crate)的编译,提升了构建多箱Rust项目时的并行度。没有流水线的编译如下图所示:
在使用了流水线后,编译如下图所示:
我承担了Rust编译器方面的工作,而Alex负责的是Cargo方面的工作。
如果你想了解其中的工作原理,以及使用方法和大量的测量数据,请阅读这篇文章:https://internals.rust-lang.org/t/evaluating-pipelined-rustc-compilation/10199
其效果高度依赖于项目的箱结构以及编译机器的配置。我们发现有些项目的性能提升可达1.84倍,而有些项目则没有任何提升。在最差的情况下,还有可能导致速度出现几乎可以忽略不计的减慢,但是无需担心,因为它不会导致任何额外的工作,只是改变了某些工作的顺序。
目前流水线编译还处于不稳定阶段,这篇文章记录了我们发现的问题:https://github.com/rust-lang/rust/issues/60988
今后的工作
我想在第三季度的时候,展开下列的工作:
对于流水线编译,我想尝试在编译器前端,尽早推动元数据的创建,这有可能会进一步提高速度。
Rust编译器经常使用memcpy,虽然不是直接使用,但生成的代码经常为了移动值或其他原因使用memcpy。编译器不会在“检查”阶段执行任何代码生成,通常有2-8%的指令会发生在memcpy中。我想了解其中的原因,并看看是否可以改进。一种可能性是编译器中移动了超大的类型;另一种可能性是代码生成有问题。如果是前一种情况,则很容易修复;如果是后一种,则难度较大。但是,如果能修改好的话,许多Rust程序都会受益。
增量编译的效率有时不是很理想。在某些工作负载上,即便做了很微小的修改,但是重新编译所需的时间与完整的非增量编译大致相同。也许我可以通过增量实现的一个小改动来提升性能。
我想看看解析器中是否还有其他可以改进的热路径,比如像#61612。
不过我还有各种Firefox的工作安排,不知道是否能顺利完成上述的所有工作。
原文:https://blog.mozilla.org/nnethercote/2019/07/17/how-to-speed-up-the-rust-compiler-in-2019/
本文为 CSDN 翻译,转载请注明来源出处。
【END】
热 文 推 荐
☞
☞
☞
☞
☞
☞
☞
☞
点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。
以上是关于我是如何提升 Rust 编译器的速度?的主要内容,如果未能解决你的问题,请参考以下文章