47

私は Web ワーカーに取り組んでおり、大量のデータを Web ワーカーに渡していますが、これには多くの時間がかかります。データを効率的に送信する方法を知りたいです。

次のコードを試しました:

var worker = new Worker('js2.js');
worker.postMessage( buffer,[ buffer]);
worker.postMessage(obj,[obj.mat2]);
if (buffer.byteLength) {
  alert('Transferables are not supported in your browser!');
}
4

2 に答える 2

42

アップデート

Chrome、Edge、および Firefox の最新バージョンは、SharedArrayBuffers をサポートするようになりました (この記事の執筆時点ではサファリではありませんが、MDN の SharedArrayBuffers を参照してください)。 transferrable (すべてのトレードオフと SharedArrayBuffers の要件については MDN を参照してください)。

更新

Mozilla によると、SharedArrayBuffer はすべての主要なブラウザーで無効になっているため、次の編集で説明されているオプションは適用されなくなりました。

Spectre への対応として、2018 年 1 月 5 日にすべての主要なブラウザで SharedArrayBuffer がデフォルトで無効になったことに注意してください。

編集:別のオプションがあり、sharedArray バッファーを送信しています。これは ES2017 の共有メモリとアトミックの一部であり、現在 FireFox 54 Nightly でサポートされています。それについて読みたい場合は、こちらをご覧ください。私はおそらく何かを書いて、私の答えに追加します。パフォーマンスのベンチマークにも追加してみます。

元の質問に答えるには:

私は Web ワーカーに取り組んでおり、大量のデータを Web ワーカーに渡していますが、これには多くの時間がかかります。データを効率的に送信する方法を知りたいです。

@MichaelDibbets answerの代わりに、彼はオブジェクトのコピーをウェブワーカーに送信し、ゼロコピーの転送可能なオブジェクトを使用しています。

データを転送可能にするつもりだったことを示していますが、うまくいかなかったと思います。そこで、あなたと将来の読者のために、一部のデータが転送可能であるとはどういう意味かを説明します。

「参照による」オブジェクトの転送 (次の引用で説明されているように、これは完全な用語ではありませんが) は、JavaScript オブジェクトだけで機能するわけではありません。転送可能なデータ型でなければなりません。

[Web Workers を使用] ほとんどのブラウザーは、構造化された複製アルゴリズムを実装しています。これにより、File、Blob、ArrayBuffer、JSON オブジェクトなどのより複雑な型を Worker の内外に渡すことができます。ただし、postMessage() を使用してこれらのタイプのデータを渡すと、コピーが作成されます。したがって、(たとえば) 50MB の大きなファイルを渡す場合、ワーカーとメイン スレッドの間でそのファイルを取得する際に顕著なオーバーヘッドが生じます。

構造化されたクローン作成は優れていますが、コピーには数百ミリ秒かかる場合があります。パフォーマンス ヒットに対抗するために、転送可能なオブジェクトを使用できます。

Transferable Objects を使用すると、データはあるコンテキストから別のコンテキストに転送されます。これはゼロコピーであり、Worker へのデータ送信のパフォーマンスを大幅に向上させます。C/C++ の世界にいる場合は、参照渡しと考えてください。ただし、参照渡しとは異なり、呼び出し元のコンテキストからの「バージョン」は、新しいコンテキストに転送されると利用できなくなります。たとえば、ArrayBuffer をメイン アプリから Worker に転送すると、元の ArrayBuffer はクリアされ、使用できなくなります。その内容は (文字通り静かに) Worker コンテキストに転送されます。

- Google の Eric Bidelman開発者、出典: html5rocks

唯一の問題は、現時点で譲渡可能なものが 2 つしかないことです。ArrayBuffer、およびMessagePort。( Canvas Proxyは、後で提供されることを願っています)。ArrayBuffers は、API を介して直接操作することはできません。型指定された配列オブジェクトまたはDataViewを作成して、バッファーに特定のビューを提供し、読み書きできるようにするために使用する必要があります。

html5rocks リンクから

転送可能なオブジェクトを使用するには、postMessage() のわずかに異なる署名を使用します。

worker.postMessage(arrayBuffer, [arrayBuffer]);

window.postMessage(arrayBuffer, targetOrigin, [arrayBuffer]);

worker の場合、最初の引数はデータで、2 番目の引数は転送するアイテムのリストです。ちなみに、最初の引数は ArrayBuffer である必要はありません。たとえば、JSON オブジェクトにすることができます。

worker.postMessage({data: int8View, moreData: anotherBuffer}, [int8View.buffer, anotherBuffer]);

それによると、あなたの

var worker = new Worker('js2.js');
worker.postMessage(buffer, [ buffer]);
worker.postMessage(obj, [obj.mat2]);

高速で実行する必要があり、ゼロコピーで転送する必要があります。唯一の問題は、bufferorがArrayBufferまたは transferrableobj.mat2でない場合です。ArrayBuffers を、バッファを使用するべきものではなく、型付き配列のビューと混同している可能性があります。

したがって、この ArrayBuffer があり、それが Int32 表現である場合。(変数にはビューというタイトルが付いていますが、DataView ではありませんが、DataView には型付き配列と同じようにプロパティ バッファーがあります。また、これが書かれた時点で、MDN は型付き配列コンストラクターを呼び出した結果に「ビュー」という名前を使用します。だから私はそれを定義する良い方法だと思った.)

var buffer = new ArrayBuffer(90000000);
var view = new Int32Array(buffer);
for(var c=0;c<view.length;c++) {
    view[c]=42;
}

これはあなたがしてはいけないことです(ビューを送信する)

worker.postMessage(view);

これはあなたがすべきことです(ArrayBufferを送信してください)

worker.postMessage(buffer, [buffer]);

これらは、plnkr でこのテストを実行した後の結果です。

Average for sending views is 144.12690000608563
Average for sending ArrayBuffers is 0.3522000042721629

編集:コメントで@Bergiが述べたように、ビューがある場合はバッファ変数はまったく必要ありません。そのように送信できるからですview.buffer

worker.postMessage(view.buffer, [view.buffer]);

ArrayBuffer が何であるかを指定する最後の引数なしで ArrayBuffer を送信するだけの将来の読者への補足として、ArrayBuffer を転送可能に送信しません。

言い換えれば、譲渡可能物を送信するときは、これが必要です:

worker.postMessage(buffer, [buffer]);

これではない:

worker.postMessage(buffer);

EDIT:バッファを送信しているので、ウェブワーカーがバッファを受信したら、バッファをビューに戻すことを忘れないでください。ビューになったら、再び操作 (読み書き) できます。

そして賞金のために:

また、firefox/chrome の公式のサイズ制限 (時間制限だけでなく) にも興味があります。ただし、賞金の対象となる元の質問に答えてください (;

特定のサイズのものを送信するための Web ブラウザーの制限については、完全にはわかりませんが、Eric Bidelman による html5rocks のエントリーの引用から、ワーカーについて話しているときに、転送可能なデータ型を使用せずに転送される 50 MB のファイルを持ち出しました。数百ミリ秒で、私のテストで示されているように、転送可能なデータ型を使用して約 1 ミリ秒でした。正直なところ、50 mb はかなり大きいです。

純粋に私自身の意見ですが、データ型自体の制限以外に、転送可能または転送不可能なデータ型で送信するファイルのサイズに制限があるとは思いません. もちろん、ブラウザがすべてをコピーする必要があり、ゼロコピーおよび転送可能でない場合、ブラウザが長時間実行されるスクリプトを停止することが最大の懸念になるでしょう。

この投稿がお役に立てば幸いです。正直なところ、私はこれまで転送可能なものについて何も知りませんでしたが、いくつかのテストと Eric Bidelman によるブログ投稿を通じてそれらを理解するのは楽しかったです。

于 2015-05-21T10:11:19.667 に答える
12

Webworkerに単一の引数を渡すまで、Webworkerにも問題がありました。

だから代わりに

worker.postMessage( buffer,[ buffer]);
worker.postMessage(obj,[obj.mat2]);

試す

var myobj = {buffer:buffer,obj:obj};
worker.postMessage(myobj);

このようにして、参照によって渡され、非常に高速であることがわかりました。データ転送に気付かずに、5 秒ごとに 1 回のプッシュで 20,000 を超えるデータ要素を前後にポストします。ただし、私はもっぱらクロムで作業しているので、他のブラウザでどのように機能するかはわかりません.

アップデート

いくつかの統計についていくつかのテストを行いました。

tmp = new ArrayBuffer(90000000);
test = new Int32Array(tmp);
for(c=0;c<test.length;c++) {
    test[c]=42;
}
for(c=0;c<4;c++) {
    window.setTimeout(function(){
        // Cloning the Array. "We" will have lost the array once its sent to the webworker. 
        // This is to make sure we dont have to repopulate it.
        testsend = new Int32Array(test);
        // marking time. sister mark is in webworker
        console.log("sending at at  "+window.performance.now());
        // post the clone to the thread.
        FieldValueCommunicator.worker.postMessage(testsend);
    },1000*c);
}

テストの結果。「遅い」を定義していないため、これが遅いカテゴリに該当するかどうかはわかりません

  • 28837.418999988586 で送信
  • 28923.06199995801 で受信
  • 86ミリ秒


  • 212387.9840001464 で送信

  • 212504.72499988973 で受信
  • 117ミリ秒


  • 247635.6210000813 で送信

  • 247760.1259998046 で受信
  • 125ミリ秒


  • 288194.15999995545で送信

  • 288304.4079998508 で受信
  • 110ミリ秒
于 2015-05-18T06:22:00.210 に答える