Edited at

WebWorkersで巨大データ転送の不思議

More than 1 year has passed since last update.

Web Workersで、worker間でデータをやりとりする際、普通はpostMessageにオブジェクトを渡すだけでことが足りる.


Simple.js

array = new Uint8Array(1000 * 1000 * 256);

// ★
worker.postMessage({
array: array
});

しかし、WebWorkersのpostMessageは参照ではなく、データの実体をコピーして飛ばすため、arrayのサイズがデカくなってくると、workers間のデータ転送のオーバヘッドが馬鹿にならない.

重たい処理をUIから分離するためにWebWorkersを活躍させたい時に困ってしまう.

そこで、登場するのがpostMessageの第2引数.

ここにTransferableなオブジェクト(Uint8ArrayFloat32ArrayがもつArrayBufferはTransferable実装)を喰わせると、データ実体ではなく、データの参照が送信されるため、転送オーバヘッドは限りなく小さくなる.


Tranferable_1.js

array = new Uint8Array(1000 * 1000 * 256);

// ★
worker.postMessage({
array: array
}, [array.buffer]);

ただ、この方法で問題なのは、一旦送信してしまうと、送信元のworkerではarrayを扱えなくなる、という点1.

送信元workerで、送信後はarrayに対して、値の参照も更新も許されなくなり、delete arrayされているのと大差ない状態になってしまう.

そこで、送信前にUint8Arrayのコピー、array2を作成し、こいつのTranferableデータを送信してみる.


Transferable_2.js

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の場合と変わらなかった.

正直、何故なのかはサッパリ判らないのだが、どっかで使えるかもしれないので残しておく.





  1. 2017.09.27追記: Shared Array Buffer が利用可能になれば解決する問題ではある