WebAssemblyとRustによるWebパフォーマンスの最適化:6ヶ月の実戦投入から学んだ教訓

Development tutorial - IT technology blog
Development tutorial - IT technology blog

JavaScriptが限界に達するとき

画像処理や行列演算モジュールをWebAssembly(Wasm)で本番運用して半年が経ち、一つの事実に気づきました。JavaScript(JS)はV8エンジンの恩恵で非常に高速ですが、物理的な限界が存在します。200万行のCSVデータ処理やブラウザ上での動画エンコードが必要な場面では、JSは力不足になりがちです。CPUの過負荷や、ガベージコレクション(GC)の予期せぬタイミングでのメモリ解放により、フレーム落ち(カクつき)が頻発します。

そこでWebAssemblyの出番です。メモリリークの懸念があるC++ではなく、私はRustを選びました。Rustの安全性とWasmの実行速度を組み合わせることで、Webアプリケーションに驚異的なパフォーマンスをもたらすことができます。

実測比較:純粋なJS、Web Workers、そしてWebAssembly

最適化を行う前に、3つの異なるアプローチをテストしました。

  • 純粋なJavaScript: 開発スピードは非常に早いが、複雑なアルゴリズムの処理では低速。
  • Web Workers: メインスレッドを解放することでUIのフリーズを防げる。しかし、アルゴリズムの実行に10秒かかる場合、バックグラウンドでも同様に10秒を消費する。
  • WebAssembly (Rust): ネイティブソフトウェアに近い実行速度。私のプロジェクトでは、行列演算のタスクが1.2秒からわずか0.15秒に短縮されました。

5万行の金融系コードをリファクタリングして得た教訓は、「Wasmを乱用しないこと」です。計算負荷の高い部分(ボトルネック)のみをWasmに移行しましょう。UIの制御やAPIの呼び出しなどは、柔軟性の高いJSに任せるのが得策です。

Wasm開発においてRustがC++やGoより優れている理由

1. ガベージコレクタ(GC)が不要

GoはWasmにコンパイル可能ですが、ランタイムが重くなりがちです。対してRustはコンパイル時に「所有権(Ownership)」の仕組みでメモリを管理します。その結果、出力される .wasm ファイルが非常に軽量になり、ページの読み込み速度が大幅に向上します。

2. 充実したツールチェーン

wasm-pack を使えば、Rustのコードをnpmパッケージとしてパッケージングするのが非常に簡単です。複雑なコンパイラ設定なしで、ReactやVueのプロジェクトに通常のJSライブラリと同じようにインポートできます。

3. 「動けば安全」という安心感

C++で大きなデータを扱うとメモリ操作ミスが起きやすいですが、Rustはコーディング段階でこれを防ぎます。ロジックが安全でない場合、コンパイラがビルドを拒否するため、本番環境での多くのバグを未然に回避できます。

実装ガイド:高性能計算モジュールの構築

まず、以下のコマンドでRust環境をインストールします:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

次に、Wasmのビルドを支援する wasm-pack をインストールします:

cargo install wasm-pack

ステップ1:プロジェクトの初期化

新しいディレクトリを作成し、Rustプロジェクトを初期化します:

cargo new --lib wasm-performance-demo
cd wasm-performance-demo

ライブラリのタイプを定義するために Cargo.toml ファイルを設定します:

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

ステップ2:Rustでロジックを記述する

CPU負荷の高い再帰処理であるフィボナッチ関数を例に、src/lib.rs に記述します:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

#[wasm_bindgen] アノテーションは架け橋の役割を果たし、JavaScriptからRustの関数を直接呼び出せるようにします。

ステップ3:WebAssemblyへのコンパイル

Web用パッケージを生成するために、以下のコマンドを実行します:

wasm-pack build --target web

完了すると、/pkg ディレクトリ内に .wasm ファイルと補助的なJSファイルが生成されます。

ステップ4:JavaScriptアプリケーションへの組み込み

このRustモジュールを index.html ファイルで以下のように使用できます:

Share: