Rust 视界 | 为 Rust 编译器提速

 

点击上方蓝字关注咱们html

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


Mozilla 工程师 Nicholas 总结了他本身迄今为止为提高 Rust 编译器的编译速度而做的改进(Pull Request)。linux

咱们能够从他所作的贡献中,对 Rust 编译器的编译细节有所了解。由于 rustc 也是 Rust 实现的,因此也能从中学习一些编写高性能 Rust 代码的经验。web

注:本文并不是完整翻译,只是重点摘录,以及针对其中的某些内容进行了一些内容扩展。算法

原文地址(点击阅读原文可达):后端

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 的使用。app

做者为了这个 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是新事物,它不是大多数操做系统的默认设置,若是咱们在更多地方使用它,几乎能够确定会出现随机的复合错误。

和去年的编译性能比较





整体而言,还不错。绿色表明性能提高,而红色则表示相反。


本文分享自微信公众号 - 觉学社(WakerGroup)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索