Next.jsでWeb-WorkerからWeb-Assembly呼び出す
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リソースを限界まで使い切ることが出来ると思われますが、一体何に使うのかさっぱり思いつきません
Discussion