Open3
Emscripten vs 同期的なコードの戦い
メインスレッド上で Asyncify
Asyncify を使うことで、ゲームループなどの無限ループを JavaScript 側にとってコルーチンみたいに取り扱えるようにしたり、JavaScript 側の非同期な処理を、C/C++ 側にとって同期的なコードに取り扱えるように、WebAssembly バイトコードを変換することができる。
#include <emscripten.h>
#include <stdio.h>
EM_ASYNC_JS(int, fetch_image, (), {
await fetch("something.png");
return 0;
});
int main() {
// JavaScript 側の非同期処理の完了を待つことができる
fetch_image();
// fetch が完了してから呼ばれるコード
return 0;
}
Asyncify の対象となる関数を直接的に呼び出す関数も、自動的に Asyncify 変換の対象となり、 Asyncify 変換処理が伝搬する。
// fetch_image は Asyncify 対象なので変換される。
int stub() {
return fetch_image();
}
// stub も Asyncify 対象なので、これも変換される。
int main() {
stub();
return 0;
}
別の関数を間接的に呼び出す関数は、-sASYNCIFY_IGNORE_INDIRECT
オプションが有効な時に限って、Asyncify 変換が行われる。しかし、このオプションを有効にした場合、呼び出される関数が Asyncify 変換の対象であるかどうかにかかわらず、変換処理が行われる。そのため、Asyncify 変換が必要でない関数も変換が行われ、コードサイズの肥大化が起こりやすい。
// fetch_image は Asyncify 対象なので変換される。
int stub() {
return fetch_image();
}
void another() {
// なんらかの Asyncify 変換のいらない処理
}
// -sASYNCIFY_IGNORE_INDIRECT が有効ならば変換される
int main() {
int (*ptr)() = &stub;
(*stub)();
return 0;
}
// これも -sASYNCIFY_IGNORE_INDIRECT が有効ならば変換されてしまう
void some() {
void (*ptr)() = &another;
(*ptr)();
}
emscripten exception handling support と組み合わせると、try/catch のあるコードが間接呼び出しに変換されるため、爆発的にコードサイズが増えてしまう。
しかし、WebAssembly Exception Handing には未対応。
PROXY_TO_WORKER を使う
長所
-
-sPROXY_TO_WORKER
を指定するだけでだいたい動く
短所
- キーボード/マウスなどのイベント処理、WebGL/canvas をつかったグラフィカルな処理などのパフォーマンスが落ちる
- ワーカースレッドから DOM まわりが全く触れず、メインスレッドにすべて移譲するため、メインスレッドとの通信コストが顕著に表れる
- SharedArrayBuffer を使うため、Web サーバ側の設定が非常に面倒
PROXY_TO_WORKER + OffScreen Canvas を使う
長所
- PROXY_TO_WORKER の便利良さを享受しつつ、WebGL/canvas をつかったグラフィカルな処理などのパフォーマンス低下も最小限に抑えられる
短所
- ゲームループなどの無限ループと組み合わせることができない
- JavaScript のイベントループに制御を戻して、初めて OffscreenCanvas の更新が行われるため
- Asyncify と組み合わせると、 Asyncify のデメリットを引き継いでしまう
- 画面更新以外の非同期処理はメインスレッドに移譲できるが、それでも面倒くささはある