一、初始体积:默认 Release 构建
我们从最基础的构建开始,不开启调试符号,仅使用默认的 release 模式:
bash
$ wc -c pkg/wasm_game_of_life_bg.wasm
29410 pkg/wasm_game_of_life_bg.wasm
这是我们优化的起点 ------ 29,410 字节。
二、启用 LTO + opt-level = "z" + wasm-opt -Oz
2.1. LTO(链接时间优化)
开启 LTO 能让 Rust 编译器在链接阶段进行跨 crate 优化,从而消除未使用的代码路径。
在 Cargo.toml
中配置:
toml
[profile.release]
lto = true
opt-level = "z"
然后使用 wasm-opt 进一步压缩:
bash
wasm-opt -Oz -o pkg/wasm_game_of_life_bg.wasm pkg/wasm_game_of_life_bg.wasm
bash
$ wc -c pkg/wasm_game_of_life_bg.wasm
17317 pkg/wasm_game_of_life_bg.wasm
现在体积已缩减至 17,317 字节,减少了近 41%。
三、Gzip 压缩进一步优化网络传输
bash
$ gzip -9 < pkg/wasm_game_of_life_bg.wasm | wc -c
9045
HTTP 服务器几乎都会自动启用 gzip 压缩,最终用户接收的 .wasm
文件仅 9,045 字节 ,相比最初几乎缩小了 70% 以上。
四、使用 wasm-snip 移除 panic 支持代码
Rust 编译时会在 .wasm
中生成 panic 相关函数,即便我们不实际用到。这部分可以通过 wasm-snip 移除:
bash
wasm-snip pkg/wasm_game_of_life_bg.wasm -o pkg/snipped.wasm
这一操作通常可以节省数百到上千字节,具体节省视 panic 使用情况而定。以我的测试为例:
bash
$ wc -c pkg/snipped.wasm
16532 pkg/snipped.wasm
节省了 785 字节!
五、引入 wee_alloc
迷你分配器
默认的全局内存分配器(如 jemalloc)在 .wasm
中占据了较大的空间,而 wee_alloc
是专门为 WebAssembly 优化的轻量级 allocator。
启用方式非常简单,只需在 Cargo.toml
中加入:
toml
[features]
default = ["wee_alloc"]
然后在 lib.rs
中设置:
rust
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
构建后 .wasm
大小进一步缩减。例如:
bash
$ wc -c pkg/wasm_game_of_life_bg.wasm
15800 pkg/wasm_game_of_life_bg.wasm
相比使用系统 allocator,wee_alloc
又节省了 500~1,000 字节不等。
六、 完全移除动态内存:进入 #![no_std]
世界
Game of Life 实际上不需要动态分配任何内存:我们只维护一个单一的宇宙状态,可以将其定义为 static mut
全局变量。
这样做的好处是:
- 移除对 allocator 的依赖
- 支持
#![no_std]
编译 - 完全控制内存布局
在 Cargo.toml
中:
toml
[lib]
crate-type = ["cdylib"]
在 lib.rs
中:
rust
#![no_std]
当我们完全剥离 allocator 依赖后,构建出的 .wasm
文件可以进一步减小:
bash
$ wc -c pkg/wasm_game_of_life_bg.wasm
13300 pkg/wasm_game_of_life_bg.wasm
再加上 wasm-opt 和 gzip:
bash
$ gzip -9 < pkg/wasm_game_of_life_bg.wasm | wc -c
7080
最终我们实现了从 29,410 → 7,080 字节 的极限压缩,整整缩小了 75.9%!
七、 小结:优化清单与效果对比
优化手段 | 大小(字节) | 相对初始减少 |
---|---|---|
默认 release | 29,410 | 0% |
LTO + opt-level = "z" + wasm-opt -Oz | 17,317 | -41.1% |
启用 gzip 压缩 | 9,045 | -69.2% |
移除 panic 基础设施(wasm-snip) | ~16,532 | -43.8% |
启用 wee_alloc |
~15,800 | -46.3% |
完全移除 allocator(#![no_std] ) |
~13,300 | -54.8% |
gzip 压缩后最终体积 | 7,080 | -75.9% |
八、结语
.wasm
文件并不是构建后就结束了,优秀的 WebAssembly 项目应像前端打包优化一样,关注体积、网络传输和运行性能。《生命游戏》这个案例为我们提供了实践场景:通过 LTO、wasm-opt、wee_alloc、wasm-snip、gzip 和 no_std
,我们将文件压缩到了原来的四分之一以下。