スポンサーリンク
Rust

Rust/WASM で高速計算デモを作ってみた

Rust
この記事は約7分で読めます。

はじめに

前回の記事では、Rust と WebAssembly(WASM)を使って「Hello」を表示する最小構成のデモを作成しました。 続編となる今回は、Rust/WASM の高速性を体感できるデモを作ってみます。

JavaScript と Rust/WASM の両方で「大量の配列の合計」を計算し、処理速度を比較します。 前回の知識をそのまま活かせる内容になっているので、ぜひ一緒に進めてみてください。最終的には意外な結果でした。

今回のゴール

  • JavaScript で 1,000 万件の配列を生成する
  • Rust/WASM に配列を渡して合計値を計算する
  • JS と WASM の処理速度を比較する
  • Rust/WASM が得意とする「数値計算」の強みを体感する

開発環境(前回と同じ)

今回も以下の環境を前提に進めます。

  • VS Code
  • Rust(rustup)
  • wasm-pack
  • ロリポップのレンタルサーバー(静的ファイル配置)

前回と同じ環境で問題ありません。

Rust プロジェクト作成(ローカル PC / VS Code)

ローカルの C:\rust\ の中で以下を実行します。

cargo new wasm-sum --lib
cd wasm-sum

次に Cargo.toml を編集します。

[package]
name = "wasm-sum"
version = "0.1.0"
edition = "2024"

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

[dependencies]
wasm-bindgen = "0.2"

Rust コード(lib.rs)

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn sum(values: Vec<f64>) -> f64 {
    values.iter().sum()
}

解説

  • JS の Float64Array が Rust の Vec<f64> に自動変換されます
  • Rust 側では普通の Vec として扱えます
  • .iter().sum() は Rust の高速イテレータ
  • 計算は WASM 側で行われるため、JS より高速になりやすいです

Rust の Vec(ベクター) は、Rust で最もよく使われる 可変長の配列(動的配列) です。 JavaScript でいうと Array に近い存在です

wasm-pack build(ローカル PCでのビルド)

wasm-pack build --target web

ローカルに pkg/ フォルダが生成されます。

ローカルに pkg/ フォルダが生成され、以下のファイルができます。

wasm-sum/pkg/
    wasm_sum.js
    wasm_sum_bg.wasm
    wasm_sum_bg.wasm.d.ts
    wasm_sum.d.ts

サーバー側のフォルダ構成(wasm.kajiblo.com)

ローカルで生成された pkg/ をサーバーにアップロードします。

最終的にサーバー側は次のようになります。

/wasm/
  ├── index.html
  ├── hello/
  ├── hello_name/
  ├── sum/                 ← 新規追加
  │     └── index.html
  ├── pkg/                 ← hello 用(既存)
  └── pkg_sum/             ← sum 用(今回追加)
        ├── wasm_sum.js
        ├── wasm_sum_bg.wasm
        ├── wasm_sum_bg.wasm.d.ts
        └── wasm_sum.d.ts

Rust/WASM は プロジェクトごとに pkg が生成されるため、 デモごとに pkg を分ける構成にしました。

sum/index.html

以下を /wasm/sum/index.html としてアップロードします。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Rust/WASM 高速計算デモ</title>
</head>
<body>
  <h1>Rust/WASM 高速計算デモ(配列の合計)</h1>
  <button id="run">計測スタート</button>
  <pre id="output"></pre>

  <script type="module">
    import init, { sum } from "../pkg_sum/wasm_sum.js";

    async function run() {
      await init();

      const output = document.getElementById("output");

      const size = 10_000_000;
      const arr = new Float64Array(size);
      for (let i = 0; i < size; i++) arr[i] = Math.random();

      const t1 = performance.now();
      let jsSum = 0;
      for (let i = 0; i < size; i++) jsSum += arr[i];
      const t2 = performance.now();

      const t3 = performance.now();
      const wasmSum = sum(arr);
      const t4 = performance.now();

      output.textContent =
        `JS 合計: ${jsSum}\n` +
        `JS 時間: ${(t2 - t1).toFixed(2)} ms\n\n` +
        `WASM 合計: ${wasmSum}\n` +
        `WASM 時間: ${(t4 - t3).toFixed(2)} ms\n`;
    }

    document.getElementById("run").onclick = run;
  </script>
</body>
</html>

/wasm/index.html に sum へのリンクを追加

<a href="./sum/" class="card">
  <div class="card-title">高速計算デモ(sum)</div>
  <div class="card-desc">1,000 万件の配列を Rust/WASM で高速に合計</div>
</a>

追加したTop画面

Rust/WASM Demo Hub

デモ画面

Rust/WASM 高速計算デモ

実行結果:JS のほうが速い?

実際に動かしてみると、JS のほうが速い結果になることがあります。 これは誤りではなく、今回のデモの構造上「JS が有利な条件」になっているためです。

JS が速い理由

1. JS → WASM のデータコピーが重い

Float64Array を WASM に渡すとき、全件コピーが発生します。 1,000 万件だとコピーだけで相当な時間がかかります。

2. JS の JIT 最適化が「単純なループ」に強い

今回の JS の処理は V8 が最適化しやすい典型的なパターンです。

for (let i = 0; i < size; i++) jsSum += arr[i];

この場合、JS はほぼ C 並の速度を出します。

3. WASM 関数呼び出しコストがある

JS → WASM の境界をまたぐため、呼び出しコストが JS より高くなります。

WASM が本領を発揮するのはどんなとき?

  • ソート(O(n log n))
  • 行列計算
  • 画像処理(ピクセル操作)
  • 暗号処理(SHA-256)
  • 物理演算

こうした 計算が重い処理では WASM が圧倒的に速くなります。

また、データを WASM 内に置きっぱなしにして コピーをなくす構造にすると、さらに高速になります。

まとめ

今回の記事では、前回の Hello デモから一歩進んで、Rust/WASM の高速性を体感できるデモを作成しました。

  • Rust/WASM は大量データの計算で JS より高速になることが多い
  • ただし今回のように「JS → WASM に大量コピーする構造」では JS が有利
  • Vec と TypedArray の連携を学べた
  • WASM が本領を発揮するのは、より重い計算や WASM 内で完結する処理

次回は、ソート画像処理 など、WASM がより速くなるデモにも挑戦したいと思っています。

コメント

タイトルとURLをコピーしました