Webassemblyマルチスレッドクレート:wasm-mtを試す
動く・・・動くぞ~
関連記事:
Rustが征くシリーズ過去記事
------------------- ↓ 前書はここから ↓-------------------
WebWokerとWebAssemblyの挙動が大分つかめた。
次は動かないモジュールを動かし考察していく。
Webassemblyマルチスレッド用crateを調べる: Rustが征く(7)で紹介した三つのモジュール
- wasm-bindgen-rayon
- wasm-mt
- wasm_thread ← よくみたら開発終わってた
これらをなんとか動かして考察していく。
ここではwasm-mtを試す。
コンパイル条件は
設定項目 | 設定値 |
---|---|
toolchain | nightly-2021-07-29 |
build.target | wasm32-unknown-unknown |
pasm-pack target | no-modules |
他の2つと異なりビルド条件が緩め。
ただ、導入難易度はダントツに高い。
(動かないよりは遙かにいいか。。。)
ヾ(・ω<)ノ" 三三三● ⅱⅲ コロコロ♪
------------------- ↓ 本題はここから ↓-------------------
プロジェクト作成
wasm-pack new wasm-mt
cd wasm-mt
cargo install cargo-edit wasm-pack wasm-bindgen-cli miniserve
cargo upgrade
cargo add wasm-mt serde_closure wasm_bindgen_futures
cargo add serde --features "derive"
cargo add web-sys --features "console"
ビルドの各種設定
ツールチェインの設定
rustup toolchain add nightly-2021-07-29
rustup component add rust-src --toolchain nightly-2021-07-29
printf '[toolchain]\nchannel = "nightly-2021-07-29"' | tee ./rust-toolchain.toml
rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-2021-07-29-x86_64-unknown-linux-gnu (override)
実装
ちょっと長いのでアコーディオン化
40億回ループしてconsoleに表示。
同期非同期と別々に実行している。
ソースコード
use wasm_mt::prelude::*;
use wasm_mt::utils::console_ln;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
#[wasm_bindgen]
pub fn app(thread_num: i32) {
spawn_local(async move {
let pkg_js = "./pkg/wasm_mt.js";
let mt = WasmMt::new(pkg_js).and_init().await.unwrap();
let _ = run_executors(mt, thread_num).await;
});
}
async fn run_executors(mt: WasmMt, thread_num: i32) -> Result<(), JsValue> {
// Prepare threads
let max_num: u64 = 40_0000_0000;
let mut v: Vec<wasm_mt::Thread> = vec![];
for i in 0..thread_num {
let th = mt.thread().and_init().await?;
th.set_id(&i.to_string());
v.push(th);
}
console_ln!("🔥 serial executor:");
for th in &v {
console_ln!("starting a thread");
let ans = exec!(th, move || {
for _i in 0..max_num {}
Ok(JsValue::from(42))
})
.await?;
console_ln!("ans: {:?}", ans);
}
console_ln!("🔥 parallel executor:");
for th in v {
spawn_local(async move {
console_ln!("starting a thread");
let ans = exec!(th, move || {
for _i in 0..max_num {}
Ok(JsValue::from(42))
})
.await
.unwrap();
console_ln!("ans: {:?}", ans);
});
}
Ok(())
}
ビルド
wasm-pack build --release --target no-modules -d ./dist/pkg
フロントエンド側の準備
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="./pkg/wasm_mt.js"></script>
<script src="./main.js"></script>
</head>
<body>
</body>
</html>
(async() => {
const maxWorkers = navigator.hardwareConcurrency || 4;
wasm_bindgen('./pkg/wasm_mt_bg.wasm').then((wasm) => {
wasm.app(maxWorkers);
});
})()
スレッド数はお好みで
結果
Webからアクセスして動作を見てみる。
miniserve ./dist
(・∀・) 動いてる動いてる
スレッド数を3にして実行してみたが、
直列ではstart - 結果を順に表示、
並列ではstartが3連続、結果が3連続。
40億回ループなんて一瞬で終わってるのが面白い。
------------------- ↓ 後書はここから ↓-------------------
Blob URLってなんだ?
さて、結果は重畳だったのだが、
Network欄を見てみると
blob:http://localhost:3000/~~~
というURLらしきものが並んでる。
(?_?) ナニコレ
調べてみるとblob URLというものがあるらしい。
バイナリファイルをロードして仮想のURLを発行。
URLを利用した各種APIに利用できる・・・らしい。
(あってるかな。)
・・・となると、wasmバイナリをロードし、
指定の数だけBlogURLを発行。
それをスレッドに見立てて動かしてるってことかな。
唯一動作確認はできたのだが、
これはマルチスレッドなのかよくわからない。
複雑な計算をしてじっくり分析が必要かもしれない。
もう少し追ってみると、
どうやらブラウザ対応を意識しているようだ。
WebWorkerは意外とIEも対応してたりするが、
例えばSharedArrayBufferなんかはsafariですら対応していない。
ビルドターゲットno-modulesをどう扱うか
以前、マニアックだから読まなくて良いよ的な記事を書いた。
wasm-packのターゲットをno-moduleにしたときの利用法
実は時系列で話すと、
マルチスレッドのcrateはかなり早い段階で発見して、
動作確認もしていたのだが、
ビルドターゲットとバンドラーをどう組み合わせるかでいろんなパターンを試した。
(ほぼほぼダメだったけど)
no-modulesはHTMLで直接使用することを前提にしているが、
バンドラーを使わず開発など個人レベルでもあり得ないので、
良い落とし所を探していた。
現状ではバンドラーが生成するjsファイルとは別にHTML上でロードして、
グローバル変数として生成される wasm_bindgen
というパラメータにwasmファイルを食わせるのが現状可能な方法だ。
一例を挙げると、
ビルド前のソースを main.ts
ビルド後のバンドラーファイルを bundle.js
とすると
const app = (async () => {
const maxWorkers = navigator.hardwareConcurrency || 4;
const path = new URL('/pkg/wasm_mt_bg.wasm', import.meta.url)
const wasm = await wasm_bindgen(path)
wasm.app(maxWorkers)
})()
HTML側で両方ロードする
<script src='/pkg/wasm_mt.js'></script>
<script defer src='/build/bundle.js'></script>
これについては別記事に書くと思う。
Discussion