Webassemblyマルチスレッドクレート:wasm-futures-executorを試す

2022/03/13に公開

エラーがでないだけでありがたい

関連記事:

Rustが征くシリーズ過去記事

------------------- ↓ 前書はここから ↓-------------------
Webassemblyマルチスレッド用crateを調べる: Rustが征く(7)で紹介した三つのモジュール

を考察してみたが、
考察、記事化してる最中に新しいモジュールが出ていた。

wasm-futures-executor

ついでだからこれも試してみようと思う。
Rustの最新バージョンではビルドできなかったので、
ビルドできたバージョンで実行している。

コンパイル条件は

設定項目 設定値
toolchain nightly-2021-12-02
build.target wasm32-unknown-unknown
build.rustflags ["-C", "target-feature=+atomics,+bulk-memory"]
unstable.build-std ["panic_abort", "std"]
wasm-pack target web

ヾ(・ω<)ノ" 三三三● ⅱⅲ コロコロ♪

------------------- ↓ 本題はここから ↓-------------------

プロジェクト作成

wasm-pack new wasm-executor
cd wasm-executor
cargo install cargo-edit wasm-pack wasm-bindgen-cli
cargo install cargo-generate --version 0.6.1
cargo upgrade
cargo add futures js-sys wasm-bindgen-futures anyhow
cargo add wasm-futures-executor --git https://github.com/wngr/wasm-futures-executor

ビルドの各種設定

ツールチェインの設定

rustup toolchain add nightly-2021-12-02
rustup component add rust-src --toolchain nightly-2021-12-02
printf '[toolchain]\nchannel = "nightly-2021-12-02"' | tee ./rust-toolchain.toml
rustup toolchain list
  stable-x86_64-unknown-linux-gnu (default)
  nightly-2021-12-02-x86_64-unknown-linux-gnu (override)

ビルドオプションの設定

./.cargo/config.toml
[build]
target = "wasm32-unknown-unknown"

[target.wasm32-unknown-unknown]
rustflags = ["-C", "target-feature=+atomics,+bulk-memory,+mutable-globals"]

[unstable]
build-std = ["panic_abort", "std"]

チェック

cargo config get -Z unstable-options
  build.target = "wasm32-unknown-unknown"
  target.wasm32-unknown-unknown.rustflags = ["-C", "target-feature=+atomics,+bulk-memory,+mutable-globals"]
  unstable.build-std = ["panic_abort", "std"]

実装

パッケージが配布しているサンプルを使って実装してみる。

./src/lib.rs
mod utils;

use futures::channel::mpsc;
use futures::StreamExt;
use js_sys::Promise;
use wasm_bindgen::prelude::*;
use wasm_futures_executor::ThreadPool;

#[wasm_bindgen]
pub async fn start() -> Result<JsValue, JsValue> {
    let pool = ThreadPool::max_threads().await?;
    let (tx, mut rx) = mpsc::channel(10);
    for i in 0..20 {
        let mut tx_c = tx.clone();
        pool.spawn_ok(async move {
            tx_c.start_send(i * i).unwrap();
        });
    }
    drop(tx);
    let mut i = 0;
    while let Some(x) = rx.next().await {
        i += x;
    }
    Ok(i.into())
}

ビルド

wasm-pack build --release --target web -d ./dist/pkg

フロントエンド側の準備

./dist/index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <script src="./js/main.mjs"></script>
</head>
<body>
</body>
</html>
./dist/main.js
import init, { start } from '/pkg/wasm_executor.js';

(async () => {
  await init();

  const res = await start();
  console.log("result", res);
})()

サーバーファイルを設置

WebAssemblyのMIMETypeとクロスオリジン問題を考慮して、
expressで頑張る系のサーバーファイルを用意

./server.cjs
const express = require('express')
const app = express()
const port = 3000

express.static.mime.define({'application/wasm': ['wasm']})

app.use(express.static('./dist', {
  setHeaders: (res) => {
    res.set('Cross-Origin-Opener-Policy', 'same-origin');
    res.set('Cross-Origin-Embedder-Policy', 'require-corp');
  }
}));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

スレッド数指定はできないのか。
そこはクライアント依存になりそう。

結果

(^_^)b いけてる

動くだけでもありがてー

右側のworkerが子スレッド。
上限の8スレッドが立って別々に計算している。

------------------- ↓ 後書はここから ↓-------------------

Rustで普通にスレッドを使うように実装できるのはいいね。
wasm-bindgen-rayonwasm_threadをリスペクトしてるらしいので、
そのあたりを意識した結果の実装なのかなと。

ビルドターゲットがwebなので、
バンドラーとの相性はいいかもしれない。

wasm-mtに比べて更新頻度が全くなく、
令和3年8月頃にリリースしてから全く動きがない。
こういうのは使うの躊躇するなぁ。
また、行進促すためのスナイプブログ書こうかしら。

最新のRustではビルドできなかったので、
そのあたりの対応をして欲しいところ。

Discussion