🪔

Next.jsでWeb-WorkerからWeb-Assembly呼び出す

2021/08/27に公開

Web-WorkerとWeb-Assemblyの連携に関して

Web-Workerを使えば、並列化による恩恵で総合的な動作速度の向上が見込めます
対してWeb-Assemblyを使えば、演算速度のような直列的な向上が見込めます
ならば「二つを合わせれば、さらなるパフォーマンスアップが見込める」というのは誰でも思うことでしょう

しかしこの二つの技術、いざ使おうとするとそれなりに面倒で敷居が高く感じます
その辺りを簡単に実現するための方法を紹介します

サンプル置き場

https://github.com/SoraKumo001/next-worker3
https://next-worker3.vercel.app/

動作画面

関連記事

Next.jsでWeb-Workerを呼び出す
Next.jsでお手軽にWeb-Workerを呼び出す
Next.jsとRustのTypeScriptなWebAssembly生活

事前準備

Rust

インストール

https://www.rust-lang.org/tools/install
これが無いと話にならないので、まずはインストールする必要があります

wasm-packのインストール

以下を実行してwasm-pack入れるとRustでWebAssemblyが簡単に生成できるようになります
cargo install wasm-pack

Rustの設定ファイルの準備

ディレクトリ構成はNext.jsと共有する形にします

  • rust/Cargo.toml
[package]
name = "rust_sums"
version = "1.0.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.76"

1~任意の数の合計を出すコード

  • rust/src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn sums(value: i64) -> i64 {
    let mut a: i64 = 0;
    for i in 1..value + 1 {
        a += i;
    }
    a
}

コンパイル

src/libs/wasmの中にビルドしたwasmファイルを出力します

wasm-pack build rust -d ../src/libs/wasm

Next.js

環境を整える

必要最低限のパッケージをインストールします

yarn init -y
yarn add next react react-dom
yarn add -D typescript @types/node @types/react

Next.jsの設定ファイルからWebPackのconfigにasyncWebAssemblyを加えます。これでWeb-Assemblyが使用可能となります
Web-Workerに関しては、特に追加設定はいりません

  • next.config.js
/**
 * @type { import("next").NextConfig}
 */
const config = {
  webpack: (config) => ({
    ...config, experiments: {
      asyncWebAssembly: true
    }
  })
};
module.exports = config;

Web-Workerの作成

  • src/libs/worker-test.ts

Web-Workerからwasmを呼び出す関数と、普通にJavaScriptで実行する関数の二種類を用意しています

import { initWorker } from "worker-lib";
import { sums } from './wasm/rust_sums_bg'

const sums_wasm = (count: number) => {
  return Number(sums(BigInt(count)))
}
const sums_javascript = (count: number) => {
  let a = BigInt(0);
  for (let i = BigInt(1); i <= count; i++) {
    a += i
  }
  return Number(a)
}

// Initialization process to make it usable in Worker.
const map = initWorker({ sums_wasm, sums_javascript })
// Export only the type
export type WorkerTest = typeof map

Next.jsのトップページの作成

ボタンを押すとWeb-Worker上からwasmとJavaScriptの関数を呼び出します
デフォルトで1~1億の合計を計算します

  • src/pages/index.tsx
import { useState } from "react";
import { createWorker } from "worker-lib";
import type { WorkerTest } from "../libs/worker-test";

// Create an instance to execute the Worker
// execute("function name",... parameter) to start the Worker
const execute = createWorker<WorkerTest>(
  () => new Worker(new URL("../libs/worker-test", import.meta.url)),
  5 // Maximum parallel number
);

const Page = () => {
  const [values, setValues] = useState<(number | string)[]>([]);
  const [a, setA] = useState(100000000);
  return (
    <div>
      <form>
        <input name="a" value={a} onChange={(e) => setA(Number(e.currentTarget.value))} />
        <button
          type="button"
          onClick={async () => {
            const index = values.length;
            setValues([...values, "running"]);
            //Calling a Worker
            const result = await execute("sums_wasm", a);
            setValues((values) => values.map((v, i) => (i === index ? result : v)));
          }}
        >
          wasm
        </button>
        <button
          type="button"
          onClick={async () => {
            const index = values.length;
            setValues([...values, "running"]);
            //Calling a Worker
            const result = await execute("sums_javascript", a);
            setValues((values) => values.map((v, i) => (i === index ? result : v)));
          }}
        >
          JavaScript
        </button>
      </form>
      {values.map((v, index) => (
        <div key={index}>{v}</div>
      ))}
    </div>
  );
};
export default Page;

動作確認

  • コマンド
yarn next

まとめ

比較的簡単にWeb-WorkerとWeb-Assemblyを組み合わせることが出来ました
これによってスレッドをぶん回しCPUリソースを限界まで使い切ることが出来ると思われますが、一体何に使うのかさっぱり思いつきません

GitHubで編集を提案

Discussion