WebAssemblyへのRust編成のビルドサイズ削減──実務レベルでの最適化チェックリスト
現場で直面するサイズ問題
WebAssemblyでRustを使う案件が増えています。パフォーマンスと安全性の両立が魅力で、特にブラウザで動作する計算処理やデータ処理が必要な場面では有力な選択肢です。ただし、実装を進めると必ず直面するのが「ビルド成果物が思ったより大きい」という課題です。
数十MBのWasmバイナリが生成されると、ネットワーク転送時間が無視できなくなります。モバイル環境での読み込み遅延、キャッシュの効率低下、ホスティングコストの増加──こうした影響は開発時には見えにくいのですが、本番運用では確実に利用者体験に響きます。
私の経験でも、複数の外部クレートに依存した処理をWasmに編成した際、最初のビルドは20MB超でした。段階的な最適化を通じて3MB程度まで圧縮できましたが、その過程で「何をどの順序で確認するか」という判断が大事だと痛感しました。この記事では、実務で役立つ最適化チェックリストを整理します。
検証の前提と観点
まず、最適化の目的を明確にしておきます。
検証の目的:
- 本番環境で許容できるサイズ(一般的には5MB以下が目安)に到達する手段を確認する
- 各最適化手法の効果を定量的に把握する
- 開発効率と最終サイズのバランスを判断する材料を得る
前提条件:
- Rustの基本的な開発環境(cargo、rustup)が整備されていること
- wasm-pack または cargo-web など、Wasmビルドツールが導入済みであること
- 測定対象は release ビルドとします(debug ビルドは比較対象外)
比較観点:
- ビルドサイズ(.wasm ファイルの生バイナリサイズ)
- gzip圧縮後のサイズ(実際のネットワーク転送量)
- ビルド時間(最適化のコストを判断するため)
チェックリスト:段階的な最適化手順
実務では「すべての最適化を一度に試す」のではなく、段階的に効果を測定しながら進めるのが重要です。以下の順序で確認することをお勧めします。
1. 不要なクレートの削除と機能フラグの確認
最初にすべきは「何が本当に必要か」の整理です。
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", default-features = false, features = ["clock"] }
ポイントは default-features = false の活用です。多くのクレートは標準で多くの機能を有効にしていますが、Wasmでは不要な機能(例えば時刻ライブラリの OS タイムゾーン対応など)が含まれます。Cargo.toml を見直して、本当に使っている機能だけを有効にするだけで、数百KBの削減が期待できます。
現場では「このクレート、実は使ってない」という発見も珍しくありません。依存関係の可視化ツール(例:cargo tree)で確認するのが効果的です。
2. リリースプロファイルの調整
Cargo.toml に release プロファイルを追加します。
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true
各パラメータの意味:
opt-level = "z": サイズ最適化を優先(デフォルトは"3"で速度優先)lto = true: リンク時最適化を有効化。ビルド時間が増えますが、サイズ削減効果は大きいcodegen-units = 1: コード生成単位を1に統一。LTOの効果を最大化strip = true: シンボル情報を削除
これらを適用すると、通常は10〜20%のサイズ削減が見込めます。
3. wasm-opt による後処理
Wasmバイナリ生成後に、Binaryen プロジェクトの wasm-opt を通すのは定番です。
wasm-pack build --target web --release
wasm-opt -Oz -o pkg/my_lib_bg.wasm pkg/my_lib_bg.wasm
-Oz フラグはサイズ最適化を最大レベルで実行します。実務では、これだけで20〜30%のサイズ削減が期待できます。ただしビルド時間は増えるため、本番ビルド時のみ適用する運用が一般的です。
4. 不要なメタデータの削除
Rustの標準ライブラリやクレートに含まれるメタデータが、Wasmでは不要な場合があります。
[profile.release]
panic = "abort"
panic = "abort" に設定すると、パニック時のスタックアンワインド情報が削除され、数百KBの削減が期待できます。Wasmではパニックの詳細なトレースが必要でない場合が多いため、実務的には有効な選択肢です。
5. 動的リンクの検討(上級)
複数のWasmモジュールを使う場合、共通部分を動的リンクすることでサイズを削減できます。ただし、これは以下の理由で慎重に検討すべきです。
- ブラウザ互換性の確認が必要
- 読み込み順序の管理が複雑になる
- 実装難度が上がり、保守コストが増える
現場では「単一モジュールで5MBを超える」という明確な制約がない限り、この段階には進まないことをお勧めします。
実測例と判断基準
実際の測定結果を示します。ある程度の規模のデータ処理ロジック(100行超のRustコード、3つの外部クレート依存)を対象にした場合:
| 段階 | サイズ(生) | サイズ(gzip) | 効果 |
|---|---|---|---|
| 初期ビルド | 22.4 MB | 5.8 MB | ベースライン |
| クレート最適化 | 20.1 MB | 5.2 MB | -10% |
| リリースプロファイル | 15.3 MB | 3.9 MB | -25% |
| wasm-opt 適用 | 12.8 MB | 3.2 MB | -18% |
| panic=abort | 12.3 MB | 3.1 MB | -4% |
重要なのは、各施策の効果が段階的に減少することです。最初の2〜3段階で大きな削減が得られ、その後は微調整の段階に入ります。
実務投入時の追加確認項目
最適化を進める際、以下の点を忘れずに確認してください。
動作検証:
最適化後、実際に機能が正しく動作するか必ずテストしてください。特に panic = "abort" や aggressive な wasm-opt オプションを使う場合、想定外の動作が発生することがあります。
ビルド時間の許容度: LTO や wasm-opt を有効化するとビルド時間が2〜5倍に増えることもあります。CI/CD パイプラインでの実行時間が許容範囲か確認が必要です。
デバッグ情報の取り扱い:
本番環境では strip = true を使いますが、デバッグ時には別ビルドプロファイルを用意し、シンボル情報を保持しておくと問題解析が容易になります。
圧縮戦略: 最終的なサイズ削減効果は、ホスティング側の gzip 圧縮設定に大きく依存します。ブラウザキャッシュの効率も含めて、運用チームと事前に相談しておくことをお勧めします。
どんな案件に向いているか
以上の最適化を実施する価値がある案件の目安:
- 向いている:
- モバイルネットワーク経由でのアクセスが多い
- 複数のWasmモジュールを並行利用する
- 初期読み込み時間が UX に直結する(金融取引、リアルタイム処理など)
- 慎重に検討すべき:
- 社内ネットワーク限定の利用
- 一度だけ読み込んでキャッシュされる用途
- 開発速度を優先する必要がある
実務では「完璧な最適化」よりも「許容できるサイズに到達し、保守可能な状態を保つ」ことが大事です。このチェックリストを参考に、案件の特性に応じて段階的に進めてください。