Help us understand the problem. What is going on with this article?

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

More than 3 years have 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 が利用可能になれば解決する問題ではある 

Quramy
Front-end web developer. TypeScript, Angular and Vim, weapon of choice.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away