一、初始體積:默認 Release 構建
我們從最基礎的構建開始,不開啟調試符號,僅使用默認的 release 模式:
$ 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
中配置:
[profile.release]
lto = true
opt-level = "z"
然后使用 wasm-opt 進一步壓縮:
wasm-opt -Oz -o pkg/wasm_game_of_life_bg.wasm pkg/wasm_game_of_life_bg.wasm
$ wc -c pkg/wasm_game_of_life_bg.wasm
17317 pkg/wasm_game_of_life_bg.wasm
現在體積已縮減至 17,317 字節,減少了近 41%。
三、Gzip 壓縮進一步優化網絡傳輸
$ 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 移除:
wasm-snip pkg/wasm_game_of_life_bg.wasm -o pkg/snipped.wasm
這一操作通常可以節省數百到上千字節,具體節省視 panic 使用情況而定。以我的測試為例:
$ wc -c pkg/snipped.wasm
16532 pkg/snipped.wasm
節省了 785 字節!
五、引入 wee_alloc
迷你分配器
默認的全局內存分配器(如 jemalloc)在 .wasm
中占據了較大的空間,而 wee_alloc
是專門為 WebAssembly 優化的輕量級 allocator。
啟用方式非常簡單,只需在 Cargo.toml
中加入:
[features]
default = ["wee_alloc"]
然后在 lib.rs
中設置:
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
構建后 .wasm
大小進一步縮減。例如:
$ 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
中:
[lib]
crate-type = ["cdylib"]
在 lib.rs
中:
#![no_std]
當我們完全剝離 allocator 依賴后,構建出的 .wasm
文件可以進一步減小:
$ wc -c pkg/wasm_game_of_life_bg.wasm
13300 pkg/wasm_game_of_life_bg.wasm
再加上 wasm-opt 和 gzip:
$ 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
,我們將文件壓縮到了原來的四分之一以下。