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 に直結する(金融取引、リアルタイム処理など)
  • 慎重に検討すべき:
    • 社内ネットワーク限定の利用
    • 一度だけ読み込んでキャッシュされる用途
    • 開発速度を優先する必要がある

実務では「完璧な最適化」よりも「許容できるサイズに到達し、保守可能な状態を保つ」ことが大事です。このチェックリストを参考に、案件の特性に応じて段階的に進めてください。