Web Workersで、worker間でデータをやりとりする際、普通はpostMessageにオブジェクトを渡すだけでことが足りる.
array = new Uint8Array(1000 * 1000 * 256);
// ★
worker.postMessage({
array: array
});
しかし、WebWorkersのpostMessageは参照ではなく、データの実体をコピーして飛ばすため、arrayのサイズがデカくなってくると、workers間のデータ転送のオーバヘッドが馬鹿にならない.
重たい処理をUIから分離するためにWebWorkersを活躍させたい時に困ってしまう.
そこで、登場するのがpostMessageの第2引数.
ここにTransferableなオブジェクト(Uint8ArrayやFloat32ArrayがもつArrayBufferはTransferable実装)を喰わせると、データ実体ではなく、データの参照が送信されるため、転送オーバヘッドは限りなく小さくなる.
array = new Uint8Array(1000 * 1000 * 256);
// ★
worker.postMessage({
array: array
}, [array.buffer]);
ただ、この方法で問題なのは、一旦送信してしまうと、送信元のworkerではarrayを扱えなくなる、という点1.
送信元workerで、送信後はarrayに対して、値の参照も更新も許されなくなり、delete arrayされているのと大差ない状態になってしまう.
そこで、送信前にUint8Arrayのコピー、array2を作成し、こいつのTranferableデータを送信してみる.
array = new Uint8Array(1000 * 1000 * 256);
// ★
array2 = new Uint8Array(array);
worker.postMessage({
array: array2
}, [array2.buffer]);
確かに元々のarrayを送信している訳ではないので、postMessage後もarrayを送信元workerで触ることは出来る.
もちろん僕も最初は「それって第2引数無で送信していたパターン(冒頭のSimple.js)と大差ないんじゃね?」と考えていた.
そこで、上記3パターンで、//★の箇所から、送信先のworker.onmessageのリスナに到達するまでの時間を計測してみた(jsfiddleに動作コードをおいておく).
256MBのデータをChromeで試した結果、下記となった.
- Simple:500msec程度
- Transferable_1: 70msec程度
- Transferable_2: 180msec程度
Transferable_1が最も速いのは当然なのだが、SimpleとTransferable_2に明らかに差がでている.
Firefoxでも試したが、数値は異なるものの傾向は傾向はChromeの場合と変わらなかった.
正直、何故なのかはサッパリ判らないのだが、どっかで使えるかもしれないので残しておく.