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の場合と変わらなかった.
正直、何故なのかはサッパリ判らないのだが、どっかで使えるかもしれないので残しておく.