在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Rust 编译缓慢的根由在于语言的设计。 我的意思并非是此乃 Rust 语言的设计目标。正如语言设计者们相互争论时经常说的那样,编程语言的设计总是充满了各种权衡。其中最主要的权衡就是:运行时性能和编译时性能。而 Rust 团队几乎总是选择运行时而非编译时。 因此,Rust 编译时间很慢。这有点让人恼火,因为 Rust 在其他方面的表现都非常好,唯独 Rust 编译时间却表现如此糟糕。 Rust 与 TiKV 的编译时冒险:第 1 集在 PingCAP,我们基于 Rust 开发了分布式存储系统 TiKV 。然而它的编译速度慢到足以让公司里的许多人不愿使用 Rust。我最近花了一些时间,与 TiKV 团队及其社区中的其他几人一起调研了 TiKV 编译时间缓慢的问题。
PingCAP 的阴影: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 次编译机会,用一次少一次)。 概览:TiKV 编译时冒险历程本系列的第一篇文章只是关于 Rust 在编译时间方面的历史演进。因为在我们深入研究 TiKV 编译时间的具体技术细节之前,可能需要更多的篇章。所以,这里先放一个漂亮的图表,无需多言。 造就编译时间缓慢的 Rust 设计Rust 编译缓慢的根由在于语言的设计。 我的意思并非是此乃 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 的编译时间,但仍有许多途径可以探索。我希望我们能持续看到进步。以下是我最近一两年所知道的一些进展。感谢所有为该问题提供帮助的人。
对于未上榜的人员或项目,我需要说一声抱歉。 下集预告所以多年来,Rust 把自己深深地逼进了一个死角,而且很可能会持续逼进,直到玩完。Rust 的编译时能否从 Rust 自身的运行时成功中得到拯救?TiKV 的构建速度能否让我的管理者满意吗? 在下一集中,我们将深入讨论 Rust 语言设计的细节,这些细节会导致它编译缓慢。 继续享受 Rust 吧,朋友们!
???? 有兴趣可点击查看 英文原版 。 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论