47

我正在研究网络工作者,我正在将大量数据传递给网络工作者,这需要很多时间。我想知道发送数据的有效方式。

我尝试了以下代码:

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(尽管在撰写本文时还没有 safari,请参阅 MDN 上的 SharedArrayBuffers),因此与可转让(您可以查看 MDN 以了解 SharedArrayBuffers 的所有权衡和要求)。

更新

根据 Mozilla,SharedArrayBuffer 已在所有主要浏览器中被禁用,因此以下 EDIT 中描述的选项不再适用。

请注意,在 2018 年 1 月 5 日,所有主要浏览器默认禁用 SharedArrayBuffer,以响应 Spectre。

编辑:现在有另一个选项,它正在发送一个 sharedArray 缓冲区。这是共享内存和原子下的 ES2017 的一部分,现在在 FireFox 54 Nightly 中得到支持。如果你想阅读它,你可以看这里。我可能会写一些东西并将其添加到我的答案中。我也会尝试添加到性能基准中。

要回答原始问题:

我正在研究网络工作者,我正在将大量数据传递给网络工作者,这需要很多时间。我想知道发送数据的有效方式。

@MichaelDibbets answer的替代方法是将对象的副本发送给网络工作者,使用零副本的可转移对象。

它表明您打算使您的数据可传输,但我猜它没有成功。因此,我将解释一些数据对于您和未来的读者可转移意味着什么。

“通过引用”传输对象(尽管这不是它的完美术语,正如在下一个引用中解释的那样)不仅仅适用于任何 JavaScript 对象。它必须是可传输的数据类型。

[With Web Workers] 大多数浏览器都实现了结构化克隆算法,它允许您将更复杂的类型传入/传出 Worker,例如 File、Blob、ArrayBuffer 和 JSON 对象。但是,当使用 postMessage() 传递这些类型的数据时,仍然会进行复制。因此,如果您要传递一个 50MB 的大文件(例如),那么在工作线程和主线程之间获取该文件会产生明显的开销。

结构化克隆很棒,但复制可能需要数百毫秒。为了对抗 perf hit,您可以使用 Transferable Objects。

使用 Transferable Objects,数据从一个上下文传输到另一个上下文。它是零拷贝,极大地提高了向 Worker 发送数据的性能。如果您来自 C/C++ 世界,请将其视为传递引用。但是,与传递引用不同,调用上下文中的“版本”一旦转移到新上下文就不再可用。例如,当将 ArrayBuffer 从您的主应用程序传输到 Worker 时,原始 ArrayBuffer 被清除并且不再可用。它的内容(从字面上看是安静的)转移到 Worker 上下文。

- Google 的 Eric Bidelman开发人员,来源:html5rocks

唯一的问题是目前只有两件事可以转让ArrayBufferMessagePort。(画布代理有望稍后出现)。ArrayBuffers 不能直接通过其 API 进行操作,应用于创建类型化数组对象DataView以将特定视图提供给缓冲区并能够对其进行读取和写入。

来自 html5rocks 链接

要使用可转移对象,请使用 postMessage() 的签名略有不同:

worker.postMessage(arrayBuffer, [arrayBuffer]);

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

工人案例,第一个参数是数据,第二个是应该转移的项目列表。顺便说一下,第一个参数不一定是 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]);

应该以极快的速度执行,并且应该以零拷贝方式传输。唯一的问题是您的bufferobj.mat2不是ArrayBuffer或可转移的。您可能会将 ArrayBuffers 与类型化数组的视图混淆,而不是您应该使用它的缓冲区。

所以如果你有这个 ArrayBuffer 并且它是 Int32 表示。(虽然变量的标题是视图,但它不是 DataView,但 DataView 确实有一个属性缓冲区,就像类型化数组一样。此外,在编写此内容时,MDN 使用名称“view”作为调用类型化数组构造函数的结果所以我认为这是定义它的好方法。)

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 而没有最后一个参数指定 ArrayBuffers 是什么你不会发送 ArrayBuffer 可转移

换句话说,在发送可转让物品时,您需要:

worker.postMessage(buffer, [buffer]);

不是这个:

worker.postMessage(buffer);

编辑:最后一点,因为您正在发送缓冲区,所以不要忘记在 webworker 收到缓冲区后将其转回视图。一旦它是一个视图,您就可以再次操作它(从中读取和写入)。

对于赏金:

我也对 firefox/chrome 的官方尺寸限制感兴趣(不仅仅是时间限制)。但是,回答原始问题有资格获得赏金(;

至于 webbrowsers 限制发送一定大小的东西,我不完全确定,但从 Eric Bidelman 在 html5rocks 上的那个条目谈到工人时,他确实提出了一个 50 mb 的文件,该文件在没有使用可传输数据类型的情况下被传输在数百毫秒内,如我的测试所示,使用可转移数据类型只需大约一毫秒。老实说,其中 50 mb 相当大。

纯粹是我自己的观点,但我不认为除了数据类型本身的限制之外,您发送的可转移或不可转移数据类型的文件大小没有限制。当然,您最大的担心可能是浏览器停止长时间运行的脚本,如果它必须复制整个内容并且不是零复制和可转移的。

希望这篇文章有所帮助。老实说,在此之前我对可转移性一无所知,但通过一些测试和 Eric Bidelman 的博客文章来弄清楚它们是很有趣的。

于 2015-05-21T10:11:19.667 回答
12

我也遇到了网络工作者的问题,直到我只向网络工作者传递了一个参数。

所以而不是

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

尝试

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

通过这种方式,我发现它通过引用传递并且速度非常快。我每 5 秒在一次推送中来回发布超过 20.000 个数据元素,而我没有注意到数据传输。不过,我一直在专门使用 chrome,所以我不知道它在其他浏览器中的表现如何。

更新

我已经对一些统计数据进行了一些测试。

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 回答