1. 为什么要关心 .wasm 大小?
- 下载时延:文件越小,用户越早拿到字节。Gzip 对 Wasm 通常可压缩 50%+。
- 解析速度:浏览器的 Baseline Compiler 会边下边编译,字节越少,启动越快。
- 总交互时间(TTI):体积只是影响 TTI 的第一环,但常最易度量与优化。
⚠️ 切勿只盯文件尺寸!Wasm 在运行时速度、baseline 解析流程等方面远优于 JavaScript,综合评估“用户可交互”才是核心。
2. 编译期瘦身:Cargo / LLVM / Binaryen
2.1 Link Time Optimization(LTO)
# Cargo.toml
[profile.release]
lto = true # 全程序链路级优化
优点:函数合并 / 死代码删除更彻底;体积更小、运行更快
缺点:编译时间上升。
2.2 LLVM 优化目标改为「尺寸优先」
取值 | 说明 | 建议 |
---|---|---|
opt-level = "s" |
优化体积 | 首选,通常比 “z” 更平衡 |
opt-level = "z" |
激进极致压缩 | 可能更小,但 CPU 性能下降,要实测 |
[profile.release]
opt-level = "s" # 或 "z"
2.3 wasm-opt(Binaryen)
# 安装(macOS / Linux)
brew install binaryen # or wget + build
# 构建后进一步压缩
wasm-opt -Os -o out.wasm in.wasm # for size
wasm-opt -Oz -o out.wasm in.wasm # 更激进
- 通常能 额外削减 15-20%,且伴随少量运行时加速。
- 默认移除
name
段;带-g
可保留调试符号。
2.4 LTO + s/z + wasm-opt 综合示例
# 1. Release 构建
wasm-pack build --release
# 2. Binaryen 二次瘦身
wasm-opt -Os -o pkg/my_bg.wasm pkg/my_bg.wasm
3. 构建后再优化:Debug Info 注意点
wasm-pack
默认移除 DWARF 信息。- 若自行
cargo build --release
需确认debug = false
,或再跑一次wasm-opt -g0
。 name
custom section 体积可达数 KB~MB;生产包务必剔除。
4. 代码体积分析
4.1 twiggy —— 栈顶 20 函数
cargo install twiggy
twiggy top -n 20 pkg/app_bg.wasm
输出示例:
Shallow% | Item
──────────┼───────────────────────────────────────────
19.65 % ┊ "function names"
6.98 % ┊ dlmalloc::Dlmalloc::malloc
5.39 % ┊ <str as fmt::Debug>::fmt
...
- Shallow Bytes / %:函数本身体积
- Retained Size (
twiggy dominators
):连带其唯一依赖可省多少
4.2 查看 LLVM-IR(找 inline 膨胀)
cargo rustc --release -- --emit=llvm-ir
find target/release -name "*.ll" | xargs less
- 观察 巨型函数 或 多次实例化的泛型。
- 内联后看不出来源时,仅 LLVM-IR 能告诉你是哪段代码拉大体积。
5. 源码级瘦身 10 连招
# | 技巧 | 说明 |
---|---|---|
1 | 避免 format! / to_string() |
fmt 引入大量 trait impl |
2 | 生产环境用静态字符串 | 调试再启用格式化 |
3 | 杜绝 panic! |
slice[i] / / / unwrap() → get() / checked_div / ? |
4 | Option::unwrap_abort() |
直接 process::abort() ,删除 panic 基础设施 |
5 | or unsafe unchecked_unwrap() (unreachable crate) |
110% 确定时使用,配合 debug 下正常 unwrap |
6 | 用 trait object 代替泛型 | 减少单态化 (monomorphization);空间换时间 |
7 | 零分配 or wee_alloc | 替换默认 dlmalloc,可省 ~10 KB |
8 | #[inline(always)] 谨慎 |
过度 inline 反而增体积 |
9 | 开启 panic = "abort" |
Cargo.toml [profile.release] |
10 | wasm-snip 定向“截肢” | wasm-snip --snip-rust-panicking + wasm-opt --dce |
6. 实战 Checklist
步骤 | 命令 / 配置 | 备注 |
---|---|---|
1 | wasm-pack build --release + lto=true + opt-level="s" |
编译期 |
2 | wasm-opt -Os |
二次瘦身 |
3 | twiggy top / dominators |
找大户 |
4 | 重构源码(fmt / panic / alloc) | 迭代 |
5 | 生产包对比 gzip -9 |
关注传输体积 |
6 | 核对 TTI / FPS | 不牺牲关键性能 |
7. 参考与工具链
- Binaryen:https://github.com/WebAssembly/binaryen
- twiggy:https://github.com/bytecodealliance/twiggy
- wee_alloc:https://github.com/rustwasm/wee_alloc
- wasm-snip:https://github.com/rustwasm/wasm-snip
- LLVM IR 语言参考:https://llvm.org/docs/LangRef.html
结语
- 先配置 → 再量化 → 后重构 —— 避免无脑猜测。
- 关注 gzip 后大小 与 Time-to-Interactive,别陷入“字节数唯⼀”误区。
- Rust + Wasm 的“可预测 + 易分析”特性,让体积优化不再黑箱。
祝你收获一个 ⼩而快 的 .wasm!