做者介绍:html
Brian Anderson 是 Rust 编程语言及其姊妹项目 Servo Web 浏览器的共同创始人之一。他目前在 PingCAP 担任高级数据库工程师。git
感谢 Rust 中文社区翻译小组对本文翻译及审校上的贡献:github
- 翻译:张汉东、黄珏珅
- 审校 :吴聪
Rust 编译缓慢的根由在于语言的设计。web
个人意思并不是是此乃 Rust 语言的设计目标。正如语言设计者们相互争论时常常说的那样,编程语言的设计老是充满了各类权衡。其中最主要的权衡就是:运行时性能和编译时性能。而 Rust 团队几乎老是选择运行时而非编译时。算法
所以,Rust 编译时间很慢。这有点让人恼火,由于 Rust 在其余方面的表现都很是好,惟独 Rust 编译时间却表现如此糟糕。数据库
在 PingCAP,咱们基于 Rust 开发了分布式存储系统 TiKV 。然而它的编译速度慢到足以让公司里的许多人不肯使用 Rust。我最近花了一些时间,与 TiKV 团队及其社区中的其余几人一块儿调研了 TiKV 编译时间缓慢的问题。编程
在 PingCAP,个人同事用 Rust 写 TiKV。它是咱们的分布式数据库 TiDB 的存储节点。采用这样的架构,是由于他们但愿该系统中做为最重要的节点,能被构造得快速且可靠,至少是在一个最大程度的合理范围内(译注:一般状况下人们认为快和可靠是很难同时作到的,人们只能在设计/构造的时候作出权衡。选择 Rust 是为了尽量让 TiKV 可以在尽量合理的状况下去提升它的速度和可靠性)。后端
这是一个很棒的决定,而且团队内大多数人对此都很是满意。设计模式
可是许多人抱怨构建的时间太长。有时,在开发模式下彻底从新构建须要花费 15 分钟,而在发布模式则须要 30 分钟。对于大型系统项目的开发者而言,这看上去可能并不那么糟糕。可是它与许多开发者从现代的开发环境中指望获得的速度相比则慢了不少。TiKV 是一个至关巨大的代码库,它拥有 200 万行 Rust 代码。相比之下,Rust 自身包含超过 300 万行 Rust 代码,而 Servo 包含 270 万行(请参阅 此处的完整行数统计)。浏览器
TiDB 中的其余节点是用 Go 编写的,固然,Go 与 Rust 有不一样的优势和缺点。PingCAP 的一些 Go 开发人员对于不得不等待 Rust 组件的构建而表示不满。由于他们习惯于快速的构建-测试迭代。
在 Go 开发人员忙碌工做的同时,Rust 开发人员却在编译时间休息(喝咖啡、喝茶、抽烟,或者诉苦)。Rust 开发人员有多余的时间来跨越心里的“阴影(译注:听说,TiKV 一天只有 24 次编译机会,用一次少一次)。
本系列的第一篇文章只是关于 Rust 在编译时间方面的历史演进。由于在咱们深刻研究 TiKV 编译时间的具体技术细节以前,可能须要更多的篇章。因此,这里先放一个漂亮的图表,无需多言。
Rust 编译缓慢的根由在于语言的设计。
个人意思并不是是此乃 Rust 语言的设计目标。正如语言设计者们相互争论时常常说的那样,编程语言的设计老是充满了各类权衡。其中最主要的权衡就是:运行时性能和编译时性能。而 Rust 团队几乎老是选择运行时而非编译时。
刻意的运行时/编译时权衡不是 Rust 编译时间差劲的惟一缘由,但这是一个大问题。还有一些语言设计对运行时性能并非相当重要,但却意外地有损于编译时性能。Rust 编译器的实现方式也抑制了编译时性能。
因此,Rust 编译时间的差劲,既是刻意为之的造就,又有出于设计以外的缘由。尽管编译器的改善、设计模式和语言的发展可能会缓解这些问题,但这些问题大多没法获得解决。还有一些偶然的编译器架构缘由致使了 Rust 的编译时间很慢,这些须要经过大量的工程时间和精力来修复。
若是迅速地编译不是 Rust 的核心设计原则,那么 Rust 的核心设计原则是什么呢?下面列出几个核心设计原则:
但这并非说设计者没有为编译速度作任何考虑。例如,对于编译 Rust 代码所要作的任何分析,团队都试图确保合理的算法复杂度。然而,Rust 的设计历史也是其一步步陷入糟糕的编译时性能沼泽的历史。
讲故事的时间到了。
我不记得本身是何时才开始意识到,Rust 糟糕的编译时间实际上是该语言的一个战略问题。在面对将来底层编程语言的竞争时可能会是一个致命的错误。在最初的几年里,我几乎彻底是对 Rust 编译器进行 Hacking(很是规暴力测试),我并不太关心编译时间的问题,我也不认为其余大多数同事会太关心该问题。我印象中大部分时间 Rust 编译时老是很糟糕,但无论怎样,我能处理好。
针对 Rust 编译器工做的时候,我一般都会在计算机上至少保留三份存储库副本,在其余全部的编译器都在构建和测试时,我就会 Hacking 其中的一份。我会开始构建 Workspace 1,切换终端,记住在 Workspace 2 发生了什么,临时作一下修改,而后再开始构建 Workspace 2,切换终端,等等。整个流程比较零碎且常常切换上下文。
这(可能)也是其余 Rust 开发者的平常。我如今对 TiKV 也常常在作相似的 Hacking 测试。
那么,从历史上看,Rust 编译时间有多糟糕呢?这里有一个简单的统计表,能够看到 Rust 的自举(Self-Hosting)时间在过去几年里发生了怎样的变化,也就是使用 Rust 来构建它本身的时间。出于各类缘由,Rust 构建本身不能直接与 Rust 构建其余项目相比,但我认为这能说明一些问题。
首个 Rust 编译器 叫作 rustboot,始于 2010 年,是用 OCaml 编写的,它最终目的是被用于构建第二个由 Rust 实现的编译器 rustc,并由此开启了 Rust 自举的历程。除了基于 Rust 编写以外,rustc 还使用了 LLVM 做为后端来生成机器代码,来代替以前 rustboot 的手写 x86 代码生成器。
Rust 须要自举,那样就能够做为一种“自产自销(Dog-Fooding)”的语言。使用 Rust 编写编译器意味着 Rust 的做者们须要在语言设计过程的早期,使用本身的语言来编写实用的软件。在实现自举的过程当中让 Rust 变成一种实用的语言。
Rust 第一次自举构建是在 2011 年 4 月 20 日。该过程总共花了 一个小时,这个编译时间对当时而言,很漫长,甚至还以为有些好笑。
最初那个超级慢的自举程序慢的有些反常,在于其包含了糟糕的代码生成和其余容易修复的早期错误(可能,我记不清了)。rustc 的性能很快获得了改善,Graydon 很快就 抛弃了旧的 rustboot 编译器 ,由于没有足够的人力和动力来维护两套实现。
在 2010 年 6 月首次发布的 11 个月以后,Rust 漫长而艰难的编译时代就此开始了。
我本想在这里分享一些有历史意义的自举时间,但在经历了数小时,以及试图从2011年开始构建 Rust 修订版的障碍以后,我终于放弃了,决定在没有它们的状况下发布这篇文章。做为补充,这里做一个类比:
反正,几个月前我构建 Rust 的时候,花了五个小时。
Rust 语言开发者们已经适应了 Rust 糟糕的自举时间,而且在 Rust 的关键早期设计阶段未能识别或处理糟糕编译时间问题的严重性。
在 Rust 项目中,咱们喜欢可以加强自身基础的流程。不管是做为语言仍是社区,这都是 Rust 取得成功的关键之一。
一个明显很是成功的例子就是 Servo。Servo 是一个基于 Rust 构建的 Web 浏览器,而且 Rust 也是为了构建 Servo 而诞生。Rust 和 Servo 是姊妹项目。它们是由同一个(初始)团队,在(大体)同一时间创造的,并同时进化。不仅是为了创造 Servo 而建立 Rust,并且 Servo 也是为了解 Rust 的设计而构建的。
这两个项目最初的几年都很是困难,两个项目都是并行发展的。此处很是适合用 忒修斯之船 作比喻——咱们不断地重建 Rust,以便在 Sevro 的海洋中畅行。毫无疑问,使用 Rust 构建 Servo 的经验,来构建 Rust 语言自己,直接促进了不少好的决定,使得 Rust 成为了实用的语言。
这里有一些关于 Servo-Rust 反馈回路的例子:
Rust 和 Servo 的共同发展创造了一个 良性循环 ,使这两个项目蓬勃发展。今天,Servo 组件被深度集成到火狐(Firefox)中,确保在火狐存活的时候,Rust 不会死去。
任务完成了。
前面提到的早期自举对 Rust 的设计一样相当重要,使得 Rust 成为构建 Rust 编译器的优秀语言。一样,Rust 和 WebAssembly 是在密切合做下开发的(我与 Emscripten 的做者,Cranelift 的做者并排工做了好几年),这使得 WASM 成为了一个运行 Rust 的优秀平台,而 Rust 也很是适合 WASM。
遗憾的是,没有这样的加强来缩短 Rust 编译时间。事实可能正好相反——Rust 越是被认为是一种快速语言,它成为最快的语言就越重要。并且,Rust 的开发人员越习惯于跨多个分支开发他们的 Rust 项目,在构建之间切换上下文,就越不须要考虑编译时间。
直到 2015 年 Rust 1.0 发布并开始获得更普遍的应用后,这种状况才真正有所改变。
多年来,Rust 在糟糕的编译时间的“温水中”被慢慢“烹煮”,当意识到它已经变得多么糟糕时,已为时已晚。已经 1.0 了,那些(设计)决策早已被锁定了。
这一节包含了太多使人厌倦的隐喻,抱歉了。
若是是 Rust 设计致使了糟糕的编译时间,那么这些设计具体又是什么呢?我会在这里简要地描述一些。本系列的下一集将会更加深刻。有些在编译时的影响比其余的更大,可是我断言,全部这些都比其余的设计耗费更多的编译时间。
如今回想起来,我不由会想,“固然,Rust 必须有这些特性”。确实,若是没有这些特性,Rust 将会是另外一门彻底不一样的语言。然而,语言设计是折衷的,这些并非注定要成 Rust 的部分。
现状并不是没有改善的但愿。一直有不少工做在努力改善 Rust 的编译时间,但仍有许多途径能够探索。我但愿咱们能持续看到进步。如下是我最近一两年所知道的一些进展。感谢全部为该问题提供帮助的人。
Rust 编译时主要问题:
MIR 级别的常量传播(constant propagation)
共享单态化(Shared monomorphizations)
Nicholas Nethercote 对 rustc 的优化工做:
对于未上榜的人员或项目,我须要说一声抱歉。
因此多年来,Rust 把本身深深地逼进了一个死角,并且极可能会持续逼进,直到玩完。Rust 的编译时可否从 Rust 自身的运行时成功中获得拯救?TiKV 的构建速度可否让个人管理者满意吗?
在下一集中,咱们将深刻讨论 Rust 语言设计的细节,这些细节会致使它编译缓慢。
继续享受 Rust 吧,朋友们!
鸣谢:不少人参与了本系列博客。特别感谢 Niko Matsakis、Graydon Hoare 和 Ted Mielczarek 的真知卓见,以及 Calvin Weng 的校对和编辑。
💡 有兴趣可点击查看 英文原版 。