bigywater.tech
Rust · 168

二进制体积分析

二进制为什么这么大?先要知道大在哪里。这篇用 size/nm 看文件的构成,用 cargo-bloat 按 crate 与函数归因,用 cargo-llvm-lines 看生成的 IR,再量依赖特征与编译选项各能省多少。

2240 KiB
本程序 release 构建大小(hello 是 443 KiB)
65%
regex 家族在 .text 中的占比(约 590 / 902 KiB)
51%
叠加 strip、panic=abort、opt-level=z、lto 后的体积缩减

文件的构成

实测 · 本机执行输出
  src/main.rs:
      1 | use serde::{Deserialize, Serialize};
      2 | #[derive(Serialize, Deserialize, Debug)]
      3 | struct P { a: i32, b: String }
      4 | fn main() {
      5 |     let arg = std::env::args().nth(1).unwrap_or_default();
      6 |     let p: P = serde_json::from_str(&arg).unwrap_or(P { a: 0, b: String::new() });
      7 |     let re = regex::Regex::new(&arg).unwrap();
      8 |     println!("{} {}", serde_json::to_string(&p).unwrap(), re.is_match("abc123"));
      9 | }
  hello(只有 println!):443 KiB;本程序:2240 KiB(约 5.1 倍)
  size -m(Mach-O 各段):
    Segment __PAGEZERO: 4294967296 (zero fill) 
    Segment __TEXT: 1458176
      Section __text: 923880
      Section __stubs: 876
      Section __stub_helper: 900
      Section __gcc_except_tab: 29952
      Section __const: 312632
      Section __cstring: 37406
      Section __unwind_info: 21712
      Section __eh_frame: 125028
    Segment __DATA_CONST: 245760
      Section __const: 231024
      Section __got: 16
    Segment __DATA: 16384
      Section __la_symbol_ptr: 584
      Section __data: 2656
  nm 看到的符号数:6362
  用 strip 命令再剥一次:2240 KiB → 1751 KiB
  剥完后 nm 的符号数:76(nm 的提示:)

LTO 与体积的关系见LTO 与二进制体积,构建配置见Cargo profile。

按 crate 归因

实测 · 本机执行输出
  退出码 0
     File  .text     Size Crate
    12.1%  31.2% 281.1KiB regex_automata
    11.8%  30.3% 273.5KiB std
     8.8%  22.7% 205.2KiB regex_syntax
     4.3%  11.1% 100.6KiB aho_corasick
     1.0%   2.5%  22.1KiB serde_json
     0.3%   0.7%   6.2KiB proc_macro2
     0.3%   0.7%   6.2KiB memchr
     0.1%   0.3%   2.9KiB regex
     0.1%   0.3%   2.9KiB [Unknown]
     0.1%   0.2%   1.9KiB u168p
     0.1%   0.2%   1.4KiB serde_core
     0.0%   0.1%   1.0KiB zmij
     0.0%   0.1%   1.1KiB And 3 more crates. Use -n N to show more.
    38.9% 100.0% 902.2KiB .text section size, the file size is 2.3MiB
    
    Note: numbers above are a result of guesswork. They are not 100% correct and never will be.

按函数归因

实测 · 本机执行输出
     File  .text     Size          Crate Name
     0.4%   1.0%   9.3KiB            std std::backtrace_rs::symbolize::gimli::resolve
     0.4%   1.0%   8.9KiB regex_automata regex_automata::meta::strategy::new
     0.4%   0.9%   8.5KiB regex_automata regex_automata::hybrid::search::find_fwd
     0.4%   0.9%   8.4KiB   regex_syntax <regex_syntax::hir::translate::TranslatorI as regex_syntax::ast::visitor::Visitor>::visit_post
     0.4%   0.9%   8.3KiB regex_automata <regex_automata::nfa::thompson::pikevm::PikeVM>::search_imp
     0.4%   0.9%   8.3KiB            std <std::backtrace_rs::symbolize::gimli::Context>::new
     0.3%   0.9%   7.7KiB regex_automata <regex_automata::nfa::thompson::pikevm::PikeVM>::which_overlapping_imp
     0.3%   0.8%   6.8KiB regex_automata <regex_automata::nfa::thompson::compiler::Compiler>::c
     0.3%   0.7%   6.6KiB            std <gimli::read::dwarf::Unit<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>, usize>>::new
     0.3%   0.7%   6.1KiB   aho_corasick <aho_corasick::dfa::Builder>::build_from_noncontiguous
    35.7%  91.7% 827.2KiB                And 2300 smaller methods. Use -n N to show more.
    38.9% 100.0% 902.2KiB                .text section size, the file size is 2.3MiB

依赖特征

实测 · 本机执行输出
  regex 特征                        release 大小(KiB)
  regex 默认特征                                 2240
  std + perf(无 unicode)                      1597
  std(无 unicode、无 perf)                      1081
  无 unicode 的版本用 [a-c]+1 运行:退出码 0,输出 {"a":0,"b":""} true
  同一个程序用 \w+ 运行(Unicode 模式的 \w 需要 unicode 特征):退出码 101
    thread 'main' (8225971) panicked at src/main.rs:7:38: called `Result::unwrap()` on an `Err` value: Syntax( ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ regex parse e

特征开关的机制见Cargo 特征开关。

编译选项

实测 · 本机执行输出
  配置                          大小(KiB)      相对上一行
  release 默认                     2240           
  + strip = true                 1751       -22%
  + panic = "abort"              1557       -11%
  + opt-level = "z"              1497        -4%
  + lto = true                   1090       -27%
  + codegen-units = 1            1090        -0%
  最终的最小版本照常运行(参数 [a-c]+1):退出码 0,输出 {"a":0,"b":""} true

LLVM IR 行数

实测 · 本机执行输出
  退出码 0;前 12 行:
      Lines                Copies             Function name
      -----                ------             -------------
      13348                406                (TOTAL)
        565 (4.2%,  4.2%)    1 (0.2%,  0.2%)  <serde_json[99b760cc00e9b90]::de::Deserializer<serde_json[99b760cc00e9b90]::read::StrRead>>::ignore_value
        396 (3.0%,  7.2%)    1 (0.2%,  0.5%)  <&mut serde_json[99b760cc00e9b90]::de::Deserializer<serde_json[99b760cc00e9b90]::read::StrRead> as serde_core[e0e85677c1f3a27b]:
        341 (2.6%,  9.8%)    1 (0.2%,  0.7%)  <<u168p[af1d42ee5f8f31f5]::P as serde_core[e0e85677c1f3a27b]::de::Deserialize>::deserialize::__Visitor as serde_core[e0e85677c1f
        308 (2.3%, 12.1%)    1 (0.2%,  1.0%)  <serde_json[99b760cc00e9b90]::de::Deserializer<serde_json[99b760cc00e9b90]::read::StrRead>>::ignore_integer
        301 (2.3%, 14.3%)    1 (0.2%,  1.2%)  <serde_json[99b760cc00e9b90]::de::Deserializer<serde_json[99b760cc00e9b90]::read::StrRead>>::peek_invalid_type
        269 (2.0%, 16.3%)    1 (0.2%,  1.5%)  <serde_json[99b760cc00e9b90]::de::Deserializer<serde_json[99b760cc00e9b90]::read::StrRead>>::parse_integer
        261 (2.0%, 18.3%)    1 (0.2%,  1.7%)  u168p[af1d42ee5f8f31f5]::main
        258 (1.9%, 20.2%)    1 (0.2%,  2.0%)  <serde_json[99b760cc00e9b90]::de::Deserializer<serde_json[99b760cc00e9b90]::read::StrRead>>::parse_exponent
        227 (1.7%, 21.9%)    1 (0.2%,  2.2%)  <serde_json[99b760cc00e9b90]::de::Deserializer<serde_json[99b760cc00e9b90]::read::StrRead>>::parse_decimal

编译时间的分析见编译时间分析。

分步演示

binary size
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。nightly 工具链用 rustup 装在隔离目录(不影响 brew 的 rustc):rustc 1.101.0-nightly (21b707e3f 2026-09-30)、miri 0.1.0 (21b707e3f9 2026-09-30)。cargo-bloat 0.12.1、cargo-llvm-lines 0.4.48(cargo install --locked,装在隔离目录);size/nm/strip 为 macOS 自带工具;选项通过 CARGO_PROFILE_RELEASE_* 环境变量设置
    • 没有验证:Linux 的 ELF 二进制(符号表与段的划分不同)、-Z build-std 重编标准库(进一步缩小 std 的部分)、upx 等压缩、cargo bloat 对 --time 的编译时间归因、符号名里的泛型实例化份数更多的程序
    • cargo-bloat 的数字是工具的估算(它自己的提示是「guesswork」),不是精确值
    京ICP备2026067502号-1京公网安备11010502063487号