如何将大数据传递给Web worker

vic*_*cky 38 javascript web-worker

我正在研究Web工作者,我正在将大量数据传递给Web worker,这需要花费大量时间.我想知道发送数据的有效方法.

我试过以下代码:

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!');
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*ohn 26

更新:

根据Mozilla的说法,SharedArrayBuffer已在所有主流浏览器中禁用,因此以下编辑中描述的选项不再适用.

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

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

回答原来的问题:

我正在研究Web工作者,我正在将大量数据传递给Web worker,这需要花费大量时间.我想知道发送数据的有效方法.

作为@MichaelDibbets的替代方案,他将对象的副本发送给webworker,使用的是一个零拷贝的可传输对象.

它表明你打算让你的数据可以转移,但我猜它没有成功.因此,我将解释一些数据对于您和未来的读者可以转移的意义.

通过引用"传递"对象(虽然这不是它的完美术语,如下一篇引言中所述)不仅适用于任何JavaScript对象.它必须是可传输的数据类型.

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

结构化克隆很棒,但副本可能需要数百毫秒.要对抗性能,可以使用可转移对象.

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

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

唯一的问题是,目前只有两件事可以转让.ArrayBufferMessagePort.(Canvas Proxies有望在以后推出).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]);
Run Code Online (Sandbox Code Playgroud)

应该以很快的速度运行,并且应该被转移零拷贝.唯一的问题是,如果您bufferobj.mat2不是ArrayBuffer或可转移.您可能会将ArrayBuffers与类型化数组的视图混淆,而不是使用其缓冲区.

所以如果你有这个ArrayBuffer并且它是Int32表示.(虽然变量标题为view,但它不是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;
}
Run Code Online (Sandbox Code Playgroud)

这是你应该什么不能做(发视图)

worker.postMessage(view);
Run Code Online (Sandbox Code Playgroud)

这是你应该做的(发送ArrayBuffer)

worker.postMessage(buffer, [buffer]);
Run Code Online (Sandbox Code Playgroud)

这些是在plnkr上运行此测试后的结果.

Average for sending views is 144.12690000608563
Average for sending ArrayBuffers is 0.3522000042721629
Run Code Online (Sandbox Code Playgroud)

编辑:正如@Bergi评论所述,如果您有视图,则根本不需要缓冲变量,因为您可以view.buffer像这样发送

worker.postMessage(view.buffer, [view.buffer]);
Run Code Online (Sandbox Code Playgroud)

正如对未来读者的一个注意事项,只是发送一个没有最后一个参数指定ArrayBuffers的ArrayBuffer,你将不会发送ArrayBuffer

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

worker.postMessage(buffer, [buffer]);
Run Code Online (Sandbox Code Playgroud)

不是这个:

worker.postMessage(buffer);
Run Code Online (Sandbox Code Playgroud)

编辑:最后一个注意事项,因为你发送一个缓冲区,不要忘记一旦webworker收到你的缓冲区就把它变回一个视图.一旦它成为一个视图,你就可以再次操作它(从中读取和写入).

并为赏金:

我也对firefox/chrome的官方大小限制感兴趣(不仅限时).然而,回答原始问题有资格获得赏金(;

至于webbrowsers限制发送一定大小的东西我不是完全确定的,但是从引用html5rocks上的条目来看Eric Bidelman在谈论工作时他确实调出了一个50 mb的文件而没有使用可传输的数据类型在数百毫秒内,通过我的测试显示,使用可传输数据类型仅在大约一毫秒内.其中50 MB是非常大的.

纯粹是我自己的看法,但我认为除了数据类型本身的限制之外,您在可传输或不可传输的数据类型上发送的文件大小不会受到限制.当然,您最担心的可能是浏览器停止长时间运行的脚本,如果它必须复制整个脚本并且不是零拷贝和可转移的.

希望这篇文章有所帮助 老实说,在此之前我对可转移物一无所知,但通过一些测试以及Eric Bidelman撰写的博客文章很有趣.

  • 不幸的是,2018年1月5日,所有主流浏览器默认使用SharedArrayBuffer来响应Spectre._您可能想要修改该编辑. (3认同)
  • 或者发送`view.buffer`,你不需要有一个额外的变量:-) (2认同)

Tsc*_*cka 8

我也遇到过webworkers的问题,直到我向Webworker传递了一个参数.

而不是

worker.postMessage( buffer,[ buffer]);
worker.postMessage(obj,[obj.mat2]);
Run Code Online (Sandbox Code Playgroud)

尝试

var myobj = {buffer:buffer,obj:obj};
worker.postMessage(myobj);
Run Code Online (Sandbox Code Playgroud)

通过这种方式,我发现它通过引用传递,并且它的速度非常快.我每隔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);
}
Run Code Online (Sandbox Code Playgroud)

测试结果.我不知道这是否属于你的慢类别,因为你没有定义"慢"

  • 发送电子邮件地址:28837.418999988586
  • 收到28923.06199995801
  • 86毫秒


  • 发送电子邮件地址为212387.9840001464

  • 收到212504.72499988973
  • 117毫秒


  • 发送电子邮件地址:247635.6210000813

  • 收到247760.1259998046
  • 125毫秒


  • 发送电子邮件地址:288194.15999995545

  • 收到288304.4079998508
  • 110毫秒

  • 是的,我的意思是,如果它实际上是通过引用传递的,则会有 0 毫秒的延迟,因为您不需要复制任何内容。当您将变量传递给函数时,通常不会等待 86 毫秒,对吧?数组缓冲区的[Transferable](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage)似乎是解决OP问题的最有效的解决方案。抱歉,对于死灵术,你的“我不知道这是否属于你的慢速类别”只是触发了我表达我的意见;-) (3认同)
  • 我想说,对于可以通过引用传递的数据来说,86 毫秒_是_慢。 (2认同)