Rust + WebAssembly:フロントエンドの計算パフォーマンスを 20 倍にする実践パス

前端工程(更新: 2026年6月2日)

なぜフロントエンドに Rust + WASM が必要なのか?

JavaScript は優れた言語ですが、計算集約型タスクにはハードリミットがあります:

タスク              JS 時間     Rust/WASM 時間    高速化率
画像圧縮(4K)         3200ms      180ms             17.8x
SHA-256(100MB)       4500ms      280ms             16.1x
JSON 解析(50MB)       890ms       52ms             17.1x
PDF レンダリング(100頁)5600ms     310ms             18.1x
正規表現(大テキスト)   1200ms       85ms             14.1x

15-20 倍のパフォーマンス向上——これが Rust + WASM の価値です。


Rust → WASM 完全ワークフロー

1. プロジェクト初期化

# ツールチェーンのインストール
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install wasm-pack

# プロジェクト作成
cargo new --lib image-processor
cd image-processor

2. Cargo.toml 設定

[package]
name = "image-processor"
version = "0.1.0"
edition = "2021"

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

[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = [
  "ImageData",
  "HtmlCanvasElement",
  "CanvasRenderingContext2d",
] }

[dependencies.image]
version = "0.25"
default-features = false
features = ["png", "jpeg"]

[profile.release]
opt-level = 3
lto = true
strip = true

3. Rust コアコード

use wasm_bindgen::prelude::*;
use image::{DynamicImage, ImageFormat};

#[wasm_bindgen]
pub struct ImageProcessor {
    image: DynamicImage,
}

#[wasm_bindgen]
impl ImageProcessor {
    #[wasm_bindgen(constructor)]
    pub fn new(data: &[u8]) -> Result<ImageProcessor, JsValue> {
        let image = image::load_from_memory(data)
            .map_err(|e| JsValue::from_str(&e.to_string()))?;
        Ok(ImageProcessor { image })
    }

    pub fn resize(&self, width: u32, height: u32) -> Result<Vec<u8>, JsValue> {
        let resized = self.image.resize_exact(width, height, image::imageops::FilterType::Lanczos3);
        let mut buf = Vec::new();
        resized.write_to(&mut buf, ImageFormat::Png)
            .map_err(|e| JsValue::from_str(&e.to_string()))?;
        Ok(buf)
    }

    pub fn grayscale(&self) -> Result<Vec<u8>, JsValue> {
        let gray = self.image.grayscale();
        let mut buf = Vec::new();
        gray.write_to(&mut buf, ImageFormat::Png)
            .map_err(|e| JsValue::from_str(&e.to_string()))?;
        Ok(buf)
    }

    pub fn compress_jpeg(&self, quality: u8) -> Result<Vec<u8>, JsValue> {
        let mut buf = Vec::new();
        let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, quality);
        encoder.encode(
            self.image.as_bytes(),
            self.image.width(),
            self.image.height(),
            self.image.color(),
        ).map_err(|e| JsValue::from_str(&e.to_string()))?;
        Ok(buf)
    }
}

4. ビルドと公開

# ビルド
wasm-pack build --target web --release

# 成果物構造
pkg/
├── image_processor.js        # JS バインディング
├── image_processor_bg.wasm   # WASM バイナリ
├── image_processor.d.ts      # TypeScript 型定義
└── package.json

5. フロントエンド統合

import init, { ImageProcessor } from './pkg/image_processor';

async function processImage(file: File) {
  await init();

  const data = new Uint8Array(await file.arrayBuffer());
  const processor = new ImageProcessor(data)?;

  // JPEG に圧縮、品質 80
  const compressed = processor.compress_jpeg(80);

  // サイズ変更
  const resized = processor.resize(800, 600);

  // グレースケール
  const grayscale = processor.grayscale();

  processor.free(); // WASM メモリ解放

  return { compressed, resized, grayscale };
}

パフォーマンス最適化のヒント

1. 頻繁な JS↔WASM 境界越えを避ける

// ❌ 悪い:ピクセルごとに 1 回の呼び出し
#[wasm_bindgen]
pub fn process_pixel(r: u8, g: u8, b: u8) -> u8 {
    (r as f32 * 0.299 + g as f32 * 0.587 + b as f32 * 0.114) as u8
}

// ✅ 良い:バッチ処理、1 回の呼び出し
#[wasm_bindgen]
pub fn process_image(data: &mut [u8]) {
    for pixel in data.chunks_exact_mut(4) {
        let gray = (pixel[0] as f32 * 0.299
                  + pixel[1] as f32 * 0.587
                  + pixel[2] as f32 * 0.114) as u8;
        pixel[0] = gray;
        pixel[1] = gray;
        pixel[2] = gray;
    }
}

2. wasm-bindgen の Vec 受け渡しを使用

// ✅ Vec<u8> を直接返す、wasm-bindgen がメモリを自動処理
#[wasm_bindgen]
pub fn generate_hash(data: &[u8]) -> Vec<u8> {
    use sha2::{Sha256, Digest};
    let mut hasher = Sha256::new();
    hasher.update(data);
    hasher.finalize().to_vec()
}

3. メモリ管理

// wasm-bindgen の自動メモリ管理を使用
#[wasm_bindgen]
pub struct Buffer {
    data: Vec<u8>,
}

#[wasm_bindgen]
impl Buffer {
    #[wasm_bindgen(constructor)]
    pub fn new(capacity: usize) -> Buffer {
        Buffer { data: Vec::with_capacity(capacity) }
    }

    pub fn as_ptr(&self) -> *const u8 {
        self.data.as_ptr()
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }
}

4. マルチスレッド WASM

// SharedArrayBuffer + COOP/COEP ヘッダーが必要
use rayon::prelude::*;

#[wasm_bindgen]
pub fn parallel_process(data: &mut [u8], width: u32) {
    let row_size = width as usize * 4;
    data.par_chunks_mut(row_size)
        .for_each(|row| {
            // 各行を並列処理
            for pixel in row.chunks_exact_mut(4) {
                // ピクセル処理...
            }
        });
}

WASM バンドルサイズ最適化

最適化手段 効果 コスト
opt-level = "z" サイズ -30% 速度 -10%
lto = true サイズ -40% コンパイル遅延
strip = true サイズ -20% デバッグ情報なし
wasm-opt -Oz サイズ -15% 追加ステップ
Tree-shaking サイズ -50%+ 正確なエクスポートが必要
wasm-snip サイズ -10% パニック処理を削除
# ベストプラクティスビルドパイプライン
wasm-pack build --target web --release
wasm-opt -Oz pkg/image_processor_bg.wasm -o pkg/image_processor_bg.wasm
wasm-snip --snip-rust-panicking-code pkg/image_processor_bg.wasm

実践例:ツールライブラリにおける WASM 応用

ツールライブラリは以下のシナリオで WASM 高速化を使用しています:

ffmpeg.wasm:動画処理

import { FFmpeg } from '@ffmpeg/ffmpeg';

const ffmpeg = new FFmpeg();
await ffmpeg.load();

// 動画トランスコード(WASM 版 ffmpeg)
await ffmpeg.writeFile('input.mp4', videoData);
await ffmpeg.exec(['-i', 'input.mp4', '-c:v', 'libx264', 'output.mp4']);
const result = await ffmpeg.readFile('output.mp4');

oxipng:PNG 圧縮

import { oxipng } from '@jsquash/oxipng';

// PNG 可逆圧縮(Rust WASM)
const compressed = await oxipng(imageData, {
  level: 4,  // 圧縮レベル 0-6
  strip: 'all', // すべてのメタデータを削除
});

JS vs WASM:どちらを選ぶべきか?

シナリオ 推奨 理由
DOM 操作 JS WASM は DOM を直接操作できない
単純な計算 JS 境界越えオーバーヘッド > 計算の利益
画像処理 WASM ピクセルレベル操作、15x+ 高速化
暗号化ハッシュ WASM 大規模データブロック処理、16x+ 高速化
圧縮アルゴリズム WASM 複雑なアルゴリズム、18x+ 高速化
データ解析 WASM 大容量ファイル解析、17x+ 高速化
文字列処理 場合による 短い文字列は JS が速い、長文は WASM が速い

判断原則:計算時間が 10ms を超え、ロジックが複雑な場合は WASM を検討。


ブラウザ互換性

ブラウザ WASM 対応 マルチスレッド SIMD 例外処理
Chrome 119+
Firefox 120+
Safari 17.2+
Edge 119+

マルチスレッド WASM には Cross-Origin-Isolation ヘッダーが必要です:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

まとめ

Rust + WebAssembly はフロントエンド開発者にネイティブに近い計算パフォーマンスをもたらします。画像処理、暗号化、圧縮などの計算集約型シナリオでは、WASM は 15-20 倍のパフォーマンス向上を実現します。しかし WASM は銀の弾丸ではありません——DOM 操作と単純な計算は依然として JS を使用すべきです。重要なのは適切なシナリオで WASM を使用すること:計算時間が 10ms を超え、ロジックが複雑な場合、Rust→WASM はフロントエンドのパフォーマンス核兵器です。

ブラウザローカルツールを無料で試す →

#Rust#WebAssembly#WASM#高性能#前端计算