前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust 视界 | 为 Rust 编译器提速

Rust 视界 | 为 Rust 编译器提速

作者头像
张汉东
发布2020-05-09 15:05:09
1.4K0
发布2020-05-09 15:05:09
举报
文章被收录于专栏:Rust 编程Rust 编程Rust 编程

理清头脑混沌,觉醒心智天地

Mozilla 工程师 Nicholas 总结了他自己迄今为止为提升 Rust 编译器的编译速度而作的改进(Pull Request)。 我们可以从他所做的贡献中,对 Rust 编译器的编译细节有所了解。因为 rustc 也是 Rust 实现的,所以也能从中学习一些编写高性能 Rust 代码的经验。 注:本文并非完整翻译,只是重点摘录,以及针对其中的某些内容进行了一些内容扩展。 原文地址: https://blog.mozilla.org/nnethercote/2020/04/24/how-to-speed-up-the-rust-compiler-in-2020/

增量编译

#68914 : 增量编译使用「SipHasher128」哈希算法来确定自上一次编译器调用以来更改了哪些代码。此PR极大地改善了从输入字节流中提取字节的过程(通过反复进行来确保它在big-endian和little-endian平台上均可工作),在大多数情况下,编译速度最多可提升13%。

在该 PR 中,Nicholas 使用一种简单的移位算法,来替代之前的缓慢算法,带来的好处是,代码量更小,消除了很多 unsafe 代码,性能也提升了。在代码的 Review过程中,还讨论了大小端字节序对哈希算法的影响。而 Rust 的 CI 跑在 ARM、x86 和 WASM 上运行测试,没有大端(big-endian)平台。但通常来说, 对于不同的 CPU 架构,Rust 默认会用对应的主机字节次序存储整形数据,而提升性能。所以,最后的讨论结果是,默认按小端序实现正确,然后留下了注释,在大端序调用相关函数的时候,需要调用方转换字节序。

#69050 :Rust 的 crate 中存储元数据(metadata)广泛使用 LEB 128 编码。但是Rustc 对其编解码的速度还不够快,这个 PR 就是减少了编解码过程中的循环次数,从而提升了性能。并且还消除了一个 Unsafe 的使用。

作者为了这个 PR ,通过使用Callgrind进行性能分析,作者发现 clap-rs-Check-CleanIncr 是受 LEB128 编码影响最大的基准测试+运行+构建组合。先后尝试了 18 种不同的方法进行分析,并且其中有 10 种方法都有性能改进效果。最终选择了现在的改进方法。

可想而知,要写出性能极致的 Rust 代码,还需要耐心且科学地分析才能做到。

LLVM 中间代码(Bitcode)

BitCode 是 LLVM 引入的一种中间代码,它是源码被编译为二进制机器码过程中的中间形态,也就是说,它既不是源码,也不是机器码。

LLVM 在编译过程中会对代码进行优化,这个优化就是基于BitCode来做。对 BitCode 进行各种类型优化,进行某种逻辑等价的交换,从而使得代码执行效率更高,体积更小。

关于 BitCode 更多介绍,可以查看这篇文章:https://xelz.info/blog/2018/11/24/all-you-need-to-know-about-bitcode/

Rust 在 rlib 和 dylib 中会存储 LLVM BitCode,以便 Rustc 能执行 跨 crate LTO(链接时优化)。

去年,作者从 Rust 的配置文件中注意到 rustc 花了一些时间来压缩它生成的LLVM BitCode,尤其是在 Debug 模式下。于是作者尝试将其更改为不去压缩 BitCode,这样可以加快一些速度,但也显着增加了磁盘上已编译工件的大小。

然后 Alex Crichton (官方人员)告诉作者一些重要的事情:编译器总会为 crate 生成目标代码和 BitCode。正常编译时使用目标代码,而通过链接时间优化(LTO)进行编译时则使用BitCode。用户只能同时而选一,因此生成两种代码通常浪费时间和磁盘空间。

于是作者发了一个 RR #66961,希望从 rlib 中不要存储 LLVM BitCode ,否则会导致增量编译的缓存过大。然而这引起了广泛的讨论,经历了七八个PR 重构之后,最终在 #71323 解决了此问题。

在 Debug 模式下,性能提升了 18% ,rlibs 磁盘占用缩减了 15% 到 20%。如果没有用 Cargo 而直接使用 rustc,则需要加 -Cbitcode-in-rlib=no 才能应用该特性。

其他改进

#67079: 改进用于热调用模式(hot calling pattern)的 shallow_resolved 函数,性能提升 2%。

#67340: 缩减 Nonterminal 字符(一般可认为是变量,可被替换的符号)大小(到40字节),在构建 serde_derive 的时候大量降低了 memcpy 的调用。性能提升 2% 。

#68694: 减少了InferCtxt中对 RefCell结构的借用,性能提升 5%。

#68848: 编译器的宏解析代码包含一个循环,该循环在每次迭代时实例化一个大型的(Parser类型的)复杂值,但是这些迭代中的大多数并没有修改该值。此PR更改了代码,因此它在循环外初始化了一个解析器值,然后使用Cow避免 Clone 它(修改迭代除外),从而使html5ever基准测试速度提高了15%。(比较有意思的是, 作者说他经常用 Cow,但是他从来却记不住关于 CoW 的使用细节,每次只能去翻文档。。

困扰链接速度提升的一个悬而未决的Bug

将 LLD (LLVM 4.0 引入的)作为链接器,可以将链接的时间成倍地提升。然而, issues 39915 报告了一个 Bug,导致至今 LLD 都无法成为 rustc 的默认链接器。

LLD 的特色:

  1. 交叉编译非常友好(重点在于嵌入式目标)。
  2. 速度非常快。对于增量编译来说,链接时间会占编译时间的一大部分,因此能把这个时间减半相当重要。

当前 Rust 和 LLD 的状态:

  1. Rust 以二进制文件发布了一个 lld 的副本,rust-lld,可以用于大多数平台
  2. rust-lld 默认以 裸机(bare metal)为目标
  3. rust-lld 默认用于 wasm
  4. 可以使用“ -C linker-flavor”明确要求使用 rust-lld

在其他地方(Linux/ Mac/ Windows)使用 LLD 的问题:

  1. lld 的 macOS 后端崩溃了,虽然已经开始重写,但还太早期
  2. 在linux / unix平台上,不应直接调用ld / lld。而应该通过系统c编译器(即gcc)来调用链接器,链接器的职责是发现像crt1.o这样的系统符号并将其提供给ld。这意味着不能“仅仅”使用rust-lld,而必须将其输入gcc / clang 等等。
  3. Windows-msvc显然还可以,并且似乎在后端使用rust-lld的支持有限,但是Rust 官方还不清楚在这里需要做什么。
  4. Windows-mingw似乎与linux / unix大致类似,除了可能会得到一个古老的GCC,而且事情有些古怪,因为伪Windows-Linux并不是经过严格测试的配置?

更一般地来说,lld是新事物,它不是大多数操作系统的默认设置,如果我们在更多地方使用它,几乎可以肯定会出现随机的复合错误。

和去年的编译性能比较

总体而言,还不错。绿色代表性能提升,而红色则表示相反。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 觉学社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档