Rust编译器性能调查2025结果
2025年9月10日 · Jakub Beránek
代表编译器性能工作组
两个月前,我们启动了首次Rust编译器性能调查,旨在帮助理解Rust开发者在构建性能方面的最大痛点。显然,这一主题对Rust社区非常重要,调查收到了超过3700份回复!我们要感谢所有参与调查的人,特别是那些通过开放式回答描述工作流程和挑战的人。我们计划每年进行一次此调查,以便观察Rust构建性能及其感知的长期趋势。
在本文中,我们将展示从调查中获得的一些有趣结果和见解,并推广我们最近已经完成或计划进行的工作,以改进Rust代码的构建性能。如果您想查看调查的完整结果,可以在此处找到。
现在请做好准备,因为有很多数据需要探索!由于本文相对较长,以下是其涵盖的主题索引:
- 整体满意度
- 重要工作流程
- 增量重建
- 类型检查和IDE性能
- 清理和CI构建
- 调试信息
- 改进构建性能的变通方案
- 理解构建缓慢的原因
整体满意度
为了解整体情绪,我们请受访者以0(最差)到10(最佳)的评分标准评价其对构建性能的满意度。平均评分为6,大多数人评价其体验为7分:
[图表数据:满意度分布]
- 1.7% (0分)
- 0.9% (1分)
- 3.5% (2分)
- 6.9% (3分)
- 7.2% (4分)
- 10.9% (5分)
- 21.3% (6分)
- 27.7% (7分)
- 14.9% (8分)
- 3.7% (9分)
- 1.3% (10分)
[PNG] [SVG]
为了更详细地了解整体构建体验,我们还分析了受访者撰写的所有开放式回答(超过一千份),以帮助我们识别几个反复出现的主题,我们将在本文中讨论这些主题。
从满意度评分和开放式回答中可以清楚地看出一件事:不同用户和工作流程的构建体验差异很大,并不像"Rust构建缓慢"那样简单。我们实际上收到了许多关于用户对Rust构建性能满意的积极评论,并赞赏其在过去几年中得到了极大改进,以至于不再成为问题。
人们也喜欢将他们的体验与其他竞争技术进行比较。例如,许多人写道,Rust的构建性能并不比C++差,甚至更好。另一方面,其他人指出,诸如Go或Zig等语言的构建性能比Rust好得多。
虽然看到一些开发人员对当前状态感到满意很棒,但显然许多人并不那么幸运,Rust的构建性能限制了他们的生产力。大约45%回答不再使用Rust的受访者表示,他们停止使用的原因至少有一个是编译时间过长。
在我们的调查中,我们收到了大量反馈,指出了构建性能若干领域的实际问题和挑战,这就是我们将在本文中重点关注的。
重要工作流程
Rust开发人员在构建性能方面遇到的挑战并不总是像编译器本身缓慢那么简单。存在许多具有竞争权衡的多样化工作流程,为它们优化构建性能可能需要完全不同的解决方案。一些改进构建性能的方法也可能相当不直观。例如,稳定某些语言特性可能有助于消除对某些构建脚本或过程宏的需求,从而加速整个Rust生态系统的编译。您可以观看RustWeek关于构建性能的此讲座以了解更多信息。
很难枚举所有可能的构建工作流程,但我们至少尝试询问了我们假设常见且可能最限制Rust开发人员生产力的工作流程:
[图表数据:工作流程对生产力的限制程度]
- Docker构建:14.39% (大问题), 44.06% (可改进), 38.55% (非问题), 3.00% (不使用)
- CI构建:10.99% (大问题), 39.47% (可改进), 40.41% (非问题), 9.13% (不使用)
- cargo test:24.76% (大问题), 46.43% (可改进), 25.49% (非问题), 3.33% (不使用)
- cargo check:20.60% (大问题), 44.41% (可改进), 27.43% (非问题), 7.56% (不使用)
- 清理未优化构建:30.93% (大问题), 38.29% (可改进), 19.19% (非问题), 11.59% (不使用)
- 清理优化构建:18.84% (大问题), 44.65% (可改进), 28.32% (非问题), 8.18% (不使用)
- 优化重建:21.66% (大问题), 40.27% (可改进), 30.25% (非问题), 7.82% (不使用)
- 工作区重建:17.27% (大问题), 32.48% (可改进), 24.09% (非问题), 26.16% (不使用)
- 未优化重建:10.95% (大问题), 19.32% (可改进), 17.69% (非问题), 52.04% (不使用)
[PNG] [SVG]
我们可以看到,我们询问的所有工作流程至少对一部分受访者造成了显著问题,但其中一些问题比其他问题更严重。为了获得关于开发人员面临的具体问题的更多信息,我们还询问了一个更详细的后续问题:
[图表数据:开发者最困扰的问题]
- cargo从头重建一切且我不理解原因:45.62% (大问题), 39.57% (可改进), 14.81% (非问题)
- 等待构建Rust代码的CI工作流程:18.25% (大问题), 38.13% (可改进), 43.62% (非问题)
- cargo check和cargo clippy不共享编译缓存:28.98% (大问题), 43.90% (可改进), 27.12% (非问题)
- cargo check和cargo build不共享编译缓存:35.93% (大问题), 37.42% (可改进), 26.66% (非问题)
- 等待IDE显示内联错误/警告注释:23.09% (大问题), 42.66% (可改进), 34.25% (非问题)
- cargo和我的IDE相互阻塞:22.32% (大问题), 39.63% (可改进), 38.04% (非问题)
- 等待小改动后的重建:23.06% (大问题), 27.54% (可改进), 49.40% (非问题)
[PNG] [SVG]
基于这两个问题的答案以及在开放式回答中分享的其他经验,我们确定了三组工作流程,接下来将讨论:
- 小改动后的增量重建
- 使用cargo check或代码编辑器进行类型检查
- 清理、从头开始的构建,包括CI构建
增量重建
在做出小的源代码更改后,等待增量重建时间过长是我们收到的开放式回答中最常见的抱怨,也是受访者表示他们最常遇到的问题。根据我们受访者的回答,这归结为三个主要瓶颈:
- 工作区中的更改触发不必要的重建。如果您修改了一个工作区中具有若干依赖crate的crate并执行重建,目前所有这些依赖crate都将不得不重新编译。这可能导致大量不必要的工作,并显著增加大型(或深层)工作区中重建的延迟。我们有一些关于如何改进此工作流程的想法,例如"重新链接,不重建"提案,但这些目前处于非常实验性的阶段。
- 链接阶段太慢。这是一个非常常见的抱怨,并且确实是一个实际问题,因为与编译过程的其余部分不同,链接总是"从头开始"执行。Rust编译器通常将链接委托给外部/系统链接器,因此其性能不完全在我们的掌控之中。然而,我们正尝试默认切换到更快的链接器。例如,最流行的目标(x86_64-unknown-linux-gnu)将很快切换到LLD链接器,这带来了显著的性能提升。长期来看,某些链接器(例如wild)可能允许我们甚至增量执行链接。
- 单个crate的增量重建太慢。此工作流程的性能取决于Rust编译器增量引擎的智能程度。虽然它已经非常复杂,但编译过程的某些部分尚未增量或未以最佳方式缓存。例如,派生过程宏的扩展当前未缓存,尽管正在进行工作以改变这一点。
一些用户提到,他们希望看到Rust执行热修补(例如Dioxus UI框架使用的subsecond系统或例如Bevy游戏引擎使用的类似方法)。虽然这些热修补系统非常令人兴奋,并且可以为特殊用例产生真正接近即时的重建时间,但应注意它们也带有许多限制和边缘情况,并且似乎尚未找到允许热修补以稳健方式工作的解决方案。
为了衡量典型的重建延迟时间,我们请受访者选择一个他们工作且导致他们在构建时间上最挣扎的单个Rust项目,并告诉我们在对代码进行更改后,他们需要等待多长时间才能重建它。
[图表数据:重建等待时间分布]
- 少于1秒:2.5%
- 1到5秒之间:20.2%
- 5到10秒之间:22.6%
- 10到30秒之间:22.5%
- 30秒到1分钟之间:12.1%
- 1到5分钟之间:15.7%
- 超过5分钟:4.5%
[PNG] [SVG]
尽管许多开发人员在每次代码更改后实际上并未经历这种延迟,因为他们在其代码编辑器中消费类型检查或内联注释的结果,但55%的受访者必须等待超过十秒才能重建的事实远非理想。
如果我们将这些结果基于其他问题的答案进行划分,很明显重建时间很大程度上取决于项目的大小:
[图表数据:基于项目大小的平均重建时间]
- 少于2千行:<1s (高比例), 1-5s (中比例), >5m (低比例)
- 2-10千行:<1s (中比例), 1-5s (高比例), >5m (低比例)
- 11-50千行:<1s (低比例), 1-5s (中比例), 10-30s (中比例)
- 51-100千行:<1s (低比例), 5-10s (中比例), 10-30s (高比例)
- 101-500千行:<1s (很低比例), 10-30s (中比例), 1-5m (中比例)
- 超过50万行:<1s (极低比例), 10-30s (中比例), >5m (高比例)
[PNG] [SVG]
并且在较小程度上也取决于使用的依赖项数量:
[图表数据:基于项目大小和依赖项数量的平均重建时间] [复杂矩阵图表显示不同代码行数和依赖项数量范围内的重建时间分布]
[PNG] [SVG]
我们希望达到这样一个点:重建Rust项目所需的时间主要取决于执行的代码更改量,而不是代码库的大小,但显然我们还没有达到那里。
类型检查和IDE性能
大约60%的受访者表示他们使用cargo终端命令来类型检查、构建或测试他们的代码,其中cargo check是在每次代码更改后最常用的命令:
[图表数据:Cargo命令使用频率和严重性]
- cargo test:36.98% (每次更改后), 26.21% (仅解决其他问题后), 11.68% (仅完成后), 32.30% (从不)
- cargo clippy:21.06% (每次更改后), 20.16% (仅解决其他问题后), 43.02% (仅完成后), 15.04% (从不)
- cargo check:45.36% (每次更改后), 41.19% (仅解决其他问题后), 35.90% (仅完成后), 20.02% (从不)
- cargo run / cargo build:26.91% (每次更改后), 12.44% (仅解决其他问题后), 9.40% (仅完成后), 2.32% (从不)
[PNG] [SVG]
一个相关的方面是代码编辑器和IDE中类型检查的延迟。大约87%的受访者表示他们使用编辑器中的内联注释作为检查编译器错误的主要机制,其中约33%的人认为等待这些注释是一个大障碍。在开放式回答中,我们还收到了许多关于Rust Analyzer性能和内存使用成为限制因素的报告。
Rust Analyzer的维护者正在努力改进其性能。其缓存系统正在改进以减少分析延迟,编辑器的分布式构建现在通过PGO进行了优化,这带来了15-20%的性能提升,并且正在进行工作以将编译器的新特质求解器集成到Rust Analyzer中,这可能最终也会导致性能提高。
超过35%的用户表示,他们认为IDE和Cargo相互阻塞是一个大问题。对此有一个现有的变通方案,您可以将Rust Analyzer配置为使用与Cargo不同的目标目录,代价是增加磁盘空间使用。我们意识到此变通方案尚未以非常明显的方式记录,因此我们将其添加到Rust Analyzer书的FAQ部分。
清理和CI构建
大约20%的参与者回应说清理构建对他们来说是一个显著的障碍。为了改进其性能,您可以尝试最近引入的实验性Cargo和编译器选项称为hint-mostly-unused,它在某些情况下可以帮助改进清理构建的性能,特别是如果您的依赖项包含大量可能实际上未被您的crate使用的代码。
清理构建可能经常发生的一个领域是持续集成(CI)。1495名受访者表示他们使用CI来构建Rust代码,其中约25%的人认为其性能对他们来说是一个大障碍。然而,几乎36%认为CI构建性能是大问题的受访者表示他们在CI中不使用任何缓存,我们对此感到惊讶。一种解释可能是生成的工件(目标目录)对于有效缓存来说太大,并且遇到了CI提供商的使用限制,这是我们在开放式回答部分反复看到提到的事情。我们最近引入了一个实验性Cargo和编译器选项称为-Zembed-metadata,旨在减少目标目录的大小,并且也正在进行工作以定期垃圾收集它们。这可能在未来有助于缓解磁盘空间使用问题。
显著减少磁盘使用的一种额外方法是减少生成的调试信息量,这使我们进入下一部分。
调试信息
默认的Cargo开发配置文件为工作区crate以及所有依赖项生成完整的调试信息(debuginfo)。这允许使用调试器逐步执行代码,但也会增加目标目录的磁盘使用,关键是它会使编译和链接变慢。这种影响可能相当大,因为我们的基准测试显示,如果我们将debuginfo级别减少到仅行表(仅生成足够的debuginfo以使回溯工作),周期数可能改进2-30%,如果我们完全禁用debuginfo生成,改进甚至更大¹。
然而,如果Rust开发人员在大多数构建后调试他们的代码,那么这种成本可能是合理的。因此我们问他们使用调试器调试Rust代码的频率:
[图表数据:调试器使用频率]
- 从不或非常罕见:53.7%
- 有时(例如每周一次或更少):31.5%
- 经常(例如每天多次):12.2%
- 几乎总是(例如几乎每次构建后):2.7%
[PNG] [SVG]
基于这些结果,似乎我们调查的受访者实际上并不那么频繁使用调试器²。
然而,当我们问人们是否要求默认生成debuginfo时,响应就不那么明确了:
[图表数据:对debuginfo的需求]
- 不,我宁愿默认使用更少的debuginfo以稍快编译:32.6%
- 是,我希望默认有完整的debuginfo:32.1%
- 是,但我不需要我的依赖项的完整debuginfo,只需要我的代码的:35.3%
[PNG] [SVG]
这就是更改默认值的问题:改进一个用户的工作流程而不使另一个用户的工作流程退步是具有挑战性的。为完整起见,以下是基于"您使用调试器的频率"问题的答案对前一个问题的答案划分:
[图表数据:基于调试器使用的debuginfo需求]
- 几乎总是:是 (高比例), 仅我的代码 (中比例), 否 (低比例)
- 经常:是 (中高比例), 仅我的代码 (中比例), 否 (中低比例)
- 有时:是 (中比例), 仅我的代码 (中高比例), 否 (中比例)
- 从不或非常罕见:是 (低比例), 仅我的代码 (中比例), 否 (高比例)
[PNG] [SVG]
令我们惊讶的是,大约四分之一的(几乎)从不使用调试器的受访者仍然希望默认生成完整的debuginfo。
当然,您始终可以手动禁用debuginfo以改进构建性能,但并非所有人都知道该选项,并且默认值非常重要。Cargo团队正在考虑改变现状的方法,例如减少开发配置文件中生成的调试信息级别,并引入一个新的内置配置文件设计用于调试。
改进构建性能的变通方案
Rust的构建性能受许多不同方面的影响,包括构建系统(通常是Cargo)和Rust编译器的配置,还包括Rust crate的组织和使用的源代码模式。因此,有几种方法可以通过使用不同的配置选项或重构源代码来改进构建性能。我们询问受访者是否甚至意识到这种可能性,是否尝试过它们以及它们的效果如何:
[图表数据:性能改进机制的使用和效果]
- 减少依赖项数量:21.82% (帮助我), 41.45% (尝试但无帮助), 11.38% (知道但未尝试), 25.35% (从未听说)
- 禁用依赖项的默认Cargo功能:13.70% (帮助我), 53.06% (尝试但无帮助), 14.02% (知道但未尝试), 19.22% (从未听说)
- 拆分crate:8.19% (帮助我), 45.75% (尝试但无帮助), 10.52% (知道但未尝试), 35.54% (从未听说)
- 创建可选的Cargo功能:35.42% (帮助我), 39.28% (尝试但无帮助), 5.40% (知道但未尝试), 19.90% (从未听说)
- 替代链接器:16.04% (帮助我), 26.93% (尝试但无帮助), 12.00% (知道但未尝试), 45.03% (从未听说)
- 减少过程宏的使用:9.58% (帮助我), 12.35% (尝试但无帮助), 12.62% (知道但未尝试), 65.45% (从未听说)
- 禁用或减少debuginfo:44.41% (帮助我), 37.75% (尝试但无帮助), 7.82% (知道但未尝试), 10.02% (从未听说)
- 减少泛型代码的使用:36.93% (帮助我), 52.81% (尝试但无帮助), 14.68% (知道但未尝试), -4.42% (从未听说?数据似乎有误)
- 缓存编译器包装器:39.94% (帮助我), 36.78% (尝试但无帮助), 12.79% (知道但未尝试), 10.49% (从未听说)
- 并行编译器前端:36.12% (帮助我), 37.08% (尝试但无帮助), 14.56% (知道但未尝试), 12.24% (从未听说)
- Cranelift后端:19.75% (帮助我), 31.34% (尝试但无帮助), 14.23% (知道但未尝试), 34.68% (从未听说)
- 共享目标目录:38.02% (帮助我), 28.54% (尝试但无帮助), 11.07% (知道但未尝试), 22.37% (从未听说)
- 合并集成测试到单个二进制:33.60% (帮助我), 36.04% (尝试但无帮助), 10.02% (知道但未尝试), 20.34% (从未听说)
[PNG] [SVG]
似乎最流行(且有效)的改进构建性能的机制是减少依赖项数量及其激活的功能,以及将较大的crate拆分为较小的crate。在不进行源代码更改的情况下改进构建性能的最常见方法似乎是使用替代链接器。似乎特别是mold和LLD链接器非常流行:
[图表数据:替代链接器使用情况]
- mold:65.3%
- lld:46.9%
- wild:6.6%
- gold:3.5%
- 其他:3.1%
[PNG] [SVG] [开放式回答的词云]
我们这里有好消息!最流行的x86_64-unknown-linux-gnu Linux目标将在下一个Rust稳定版本中开始使用LLD链接器,从而默认实现更快的链接时间。随着时间的推移,我们将能够评估此更改对整个Rust生态系统的破坏性如何,以及我们是否可以例如切换到不同的(甚至更快的)链接器。
构建性能指南
我们对相对较多的用户不知道某些改进编译时间的方法感到惊讶,特别是那些非常容易尝试且通常不需要源代码更改的方法(例如减少debuginfo或使用不同的链接器或代码生成后端)。此外,几乎42%的受访者没有尝试使用任何改进构建性能的机制。虽然这并非完全出乎意料,因为其中一些机制需要使用nightly工具链或对源代码进行非平凡更改,但我们认为原因之一也仅仅是Rust开发人员可能不知道这些机制可用。在开放式回答中,几个人还指出,如果Rust项目关于此类改进编译时间的机制有某种官方指导,他们将不胜感激。
应注意,我们询问的机制实际上是呈现各种权衡的变通方案,应始终仔细考虑这些权衡。一些人在开放式回答中对其中一些变通方案表示不满,因为他们发现仅仅为了达到合理的编译时间而修改代码(有时可能导致例如增加维护成本或更差的运行时性能)是不可接受的。然而,这些变通方案在某些情况下仍然非常有用。
我们收到的反馈表明,在Rust社区中更多地传播对这些机制的认识可能是有益的,因为其中一些可以对构建性能产生非常大的影响,但也要坦率地解释它们引入的权衡。尽管在线已经存在几个涵盖此主题的优秀资源,我们决定创建一个用于优化构建性能的官方指南(目前进行中),该指南可能托管在Cargo书中。本指南旨在提高对各种改进构建性能机制的认识,并提供一个评估其权衡的框架。
我们的长期目标是使编译如此之快,以至于绝大多数用例不再需要类似的变通方案。然而,没有免费的午餐,Rust强大类型系统保证的组合、其编译模型以及对运行时性能的高度关注通常与非常快的(重新)构建性能相悖,并且可能需要使用至少一些变通方案。我们希望本指南将帮助Rust开发人员了解它们并为其特定用例评估它们。
理解构建缓慢的原因
当Rust开发人员遇到构建缓慢时,识别编译过程究竟在何处花费时间以及可能的瓶颈是什么可能具有挑战性。似乎只有极少数Rust开发人员利用工具来分析其构建:
[图表数据:构建分析工具的意识和使用情况]
- Cargo计时(cargo build –timings):29.55% (使用), 25.78% (知道但不使用), 44.67% (从未听说)
- 编译器自分析器(-Zself-profile):23.01% (使用), 34.28% (知道但不使用), 42.71% (从未听说)
- 编译器传递分析(-Ztime-passes):18.93% (使用), 64.33% (知道但不使用), 16.74% (从未听说)
- LLVM函数实例化分析(cargo-llvm-lines):6.13% (使用), 68.82% (知道但不使用), 25.05% (从未听说)
- 编译器传递分析(-Ztime-passes):5.40% (使用), 70.86% (知道但不使用), 23.74% (从未听说)
- LLVM函数实例化分析(cargo-llvm-lines):6.13% (使用), 46.79% (知道但不使用), 47.08% (从未听说)
[PNG] [SVG]
这 hardly comes as a surprise。目前没有那么多直观理解Cargo和rustc性能特征的方法。一些工具仅提供有限量的信息(例如cargo build --timings),而其他工具的输出(例如-Zself-profile)在没有编译器内部知识的情况下非常难以解释。
为了稍微改善这种情况,我们最近添加了对显示链接时间到cargo build --timings输出的支持,以提供关于crate编译中可能瓶颈的更多信息(注意此功能尚未稳定)。
长期来看,拥有可以帮助Rust开发人员诊断其crate中编译瓶颈而无需理解编译器如何工作的工具将是很好的。例如,它可以帮助回答诸如"在给定的源代码更改后必须重新编译哪些代码"或"哪些(过程)宏需要最长的时间来扩展或产生最大的输出"等问题,并理想地甚至提供一些可操作的