bigywater.tech
Rust · 167

编译时间分析

编译慢,先要知道慢在哪里。这篇把 cargo 的 --timings 报告拆开,找出关键路径;再用 rustc 的 -Z time-passes 看各阶段;最后量几个常见旋钮(并行度、泛型、debuginfo、优化级别、增量)到底有多大效果。

5.05 秒
serde + serde_json + regex 的一次干净构建(--timings)
3.7×
-j 1 相对默认并行的耗时倍数
0.20 秒
改 main.rs 一个字符串后的增量重编

cargo build --timings 与关键路径

实测 · 本机执行输出
  src/main.rs:
      1 | use serde::{Serialize, Deserialize};
      2 | #[derive(Serialize, Deserialize, Debug)]
      3 | struct P { a: i32, b: String }
      4 | fn main() {
      5 |     let p = P { a: 1, b: "x".into() };
      6 |     let s = serde_json::to_string(&p).unwrap();
      7 |     let re = regex::Regex::new(r"\d+").unwrap();
      8 |     println!("{} {}", s, re.is_match("a1"));
      9 | }
  Cargo.toml 依赖: serde = { version = "1", features = ["derive"] };  serde_json = "1";  regex = "1"
  cargo build --timings:退出码 0;整体 5.1 秒;最后一行:Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.05s
  HTML 报告大小 47 KiB,共 28 个编译单元(build script 的编译、运行与库各算一个)
  单元                     类型                开始(s)    耗时(s)    结束(s)
  serde_core 1.0.229     build script       1.89     0.20     2.09
  itoa 1.0.18            lib                1.89     0.11     2.00
  memchr 2.8.3           lib                1.89     0.30     2.19
  proc-macro2 1.0.107    build script       1.89     0.22     2.11
  zmij 1.0.23            build script       1.89     0.20     2.09
  quote 1.0.47           build script       1.89     0.19     2.08
  unicode-ident 1.0.26   lib                1.89     0.07     1.96
  regex-syntax 0.8.11    lib                1.89     1.11     3.00
  serde_json 1.0.151     build script       1.89     0.17     2.06
  serde 1.0.229          build script       1.89     0.19     2.08
  serde_json 1.0.151     build-script (run)     2.06     0.09     2.15
  quote 1.0.47           build-script (run)     2.08     0.13     2.21
  zmij 1.0.23            build-script (run)     2.09     0.13     2.22
  serde 1.0.229          build-script (run)     2.09     0.13     2.22
  serde_core 1.0.229     build-script (run)     2.09     0.13     2.22
  proc-macro2 1.0.107    build-script (run)     2.11     0.25     2.36
  aho-corasick 1.1.5     lib                2.12     0.68     2.80
  serde_core 1.0.229     lib                2.22     1.06     3.28
  zmij 1.0.23            lib                2.22     0.17     2.39
  proc-macro2 1.0.107    lib                2.36     0.24     2.60
  quote 1.0.47           lib                2.54     0.16     2.70
  syn 3.0.6              lib                2.65     0.84     3.49
  regex-automata 0.4.18  lib                2.73     1.32     4.05
  serde_json 1.0.151     lib                3.22     0.52     3.74
  serde_derive 1.0.229   lib                3.49     0.98     4.47
  regex 1.13.1           lib                3.67     0.24     3.91
  serde 1.0.229          lib                4.47     0.39     4.86
  u167p 0.1.0            u167p "bin"        4.86     0.19     5.05
  最后一个单元结束于 5.05 秒;同一时刻最多并行 10 个单元;单元耗时总和 10.41 秒
  有分段信息的单元里:frontend 合计 5.59 秒、codegen 合计 1.62 秒
  最后开始的单元是 u167p(开始于 4.86 秒):它要等所有依赖的 rmeta 就绪
  直接解锁 u167p 的单元:['serde']

增量编译的原理见增量编译,构建配置见Cargo profile。

并行度

实测 · 本机执行输出
  -j 1   3 次:11.8, 11.7, 11.9 秒;中位数 11.8 秒
  默认     3 次:3.2, 3.2, 3.2 秒;中位数 3.2 秒

rustc -Z time-passes

实测 · 本机执行输出
  退出码 0;共 39 个阶段(另有 total = 0.161 秒;link / link_crate / link_binary 分别为 0.047/0.047/0.047 秒);耗时前 10:
  阶段                                               秒
  finish_ongoing_codegen                       0.047
  LLVM_passes                                  0.047
  run_linker                                   0.040
  MIR_borrow_checking                          0.019
  type_check_crate                             0.016
  codegen_crate                                0.014
  coherence_checking                           0.008
  monomorphization_collector_graph_walk        0.007
  get_apple_sdk_root                           0.006
  codegen_to_LLVM_IR                           0.005

各编译阶段的含义见Rust 编译的几个阶段。

泛型实例化

实测 · 本机执行输出
      N     泛型 (秒)    dyn (秒)           泛型单态化项数        dyn 单态化项数
     50       0.10       0.10                50                1
    200       0.15       0.13               200                1
    800       0.35       0.29               800                1

泛型单态化的细节见单态化与代码生成。

构建配置

实测 · 本机执行输出
  配置                                      耗时(秒)       可执行文件(KiB)
  dev(默认,debuginfo=2)                       3.3             5869
  dev + debug=0                             3.3             4948
  dev + opt-level=1                         5.1             2781
  release                                   4.8             2140

增量重编

实测 · 本机执行输出
  干净构建 3.6 秒
  没有任何改动再构建:0.03 秒
  改 main.rs 里的一个字符串:0.20 秒,重编 1 个包
  给 serde_json 加 preserve_order 特征:2.63 秒,重编 5 个包:serde_json, hashbrown, equivalent, indexmap, u167p

分步演示

compile time
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。cargo 1.98.1 稳定版;time-passes 与 print-mono-items 用 RUSTC_BOOTSTRAP=1;本机 10 核
    • 没有验证:更大的真实项目(几百个依赖)上的结果、cargo build --timings=json 的机器可读输出、-Z threads 并行前端、使用 lld/mold 等其它链接器的效果、sccache 等缓存工具、-Z self-profile 的细粒度分析
    • 同一个项目的干净构建:第一节带 --timings 的那次是 5.05 秒,第二节不带 --timings 的默认并行 3 次中位数是 3.2 秒。没有验证这个差别是 --timings 本身的开销、第一次运行的缓存状态还是别的原因,所以文中把第一节的各单元时间当作「这一次运行里的相对关系」
    京ICP备2026067502号-1京公网安备11010502063487号