web worker和主线程的数据交换效率初探

概述

web worker是浏览器的多线程机制,多用于处理不涉及DOM操作的密集计算任务,例如算法计算,数据请求处理等,我正在开发的开源地图引擎 maptalks.js 中对地图数据的处理就很适合放到web worker中(突兀的硬广 ^_^)。

web worker最大的优点是形式简单:

  • worker的运行上下文(context)与主线程独立,无法互相调用,避免了多线程编程中的线程锁等复杂机制
  • worker只能通过postMessage和onmessage与主线程交换数据

所以,worker程序的性能很大程度上取决于与主线程之间数据交换效率。

  • 默认情况下,postMessage中数据是采用结构化克隆(structrured clone)算法来实现跨进程传送的。
  • 引进Web Worker接口时,浏览器同时引进了Transferable和ArrayBuffer这一对好基友,当传送的数据为Transferable时,浏览器能通过只传送数据引用(指针),而不是结构化克隆(structrured clone)来极大的提高数据传送效率,具体可以参考google的Transferable Objects: Lightning Fast!(需翻墙)。

问题

当传送数据结构不复杂时(类型化数组,字符串等),我们有两种可选的数据传送方式:

  • 直接用postMessage({ data })传送,让浏览器用structured clone算法拷贝数据到主线程
  • 参考谷歌文章,将数据先转化为ArrayBuffer(Transferable),然后直接传引用给主线程,无需structured clone

如果数据本身已经是类型化数组,毫无疑问方式二更好。

但如果有很多简单数据(类型化数组或字符串),将这些数据转化为ArrayBuffer后采用Transferable传送,相比浏览器的structured clone,哪个效率更高?

测试

为此,我写了一个jsperf测试程序。

测试数据为100K字节的整数数组和200K字节的字符串数组,创建以下四个测试用例:

  1. 封装成对象,直接传送数据 (structured clone传送)
  2. 拷贝为新的数组后,封装成对象传送 (structured clone传送)
  3. 将数组转化为ArrayBuffer,转化方式用传统的遍历循环数据 (Transferable传送)
  4. 将数组转化为ArrayBuffer,转化方式用TypedArray.prototype.set批量转化 (Transferable传送)

测试结果取决于浏览器:

  • 在chrome 64和firefox 58下, 都是方式4最快,但chrome上方式3最慢
  • firefox下方式1,2最慢,与3,4差距很大
  • 在ie 11下,1最快,3最慢,但4与1的差距并不大

结论

  • 综合来看,数据结构简单时,转化成ArrayBuffer,用Transferable方式传送性能更好
  • ArrayBuffer的转化方式很重要,用Array.prototype.set,而不是循环遍历
  • 除了100K + 200K,也测试了其他数据,结论仍然成立

最后附上我本机的测试结果:

Chrome 64:

web worker和主线程的数据交换效率初探

Firefox 58:

web worker和主线程的数据交换效率初探

IE11:

web worker和主线程的数据交换效率初探

相关推荐