将一个大的数据块复制到工作节点上是否昂贵?

7
使用Fetch API,我可以发起一个网络请求以获取大量二进制数据(例如超过500 MB),然后将响应转换为BlobArrayBuffer
接下来,我可以使用worker.postMessage方法,让标准结构化克隆算法复制Blob到Web Worker,或将ArrayBuffer传输到工作线程上下文中(从而使其不再在主线程中可用)。
一开始,似乎最好将数据作为ArrayBuffer获取,因为Blob无法传输,因此需要复制。但是,Blob是不可变的,因此浏览器似乎没有将其存储在与页面关联的JS堆中,而是存储在专用的Blob存储空间中。因此,复制到工作线程上下文中的实际上只是一个引用。
我已经准备了一个演示来尝试这两种方法之间的差异:https://blobvsab.vercel.app/。我正在使用这两种方法获取656 MB的二进制数据。

我在本地测试中观察到的有趣现象是,复制 Blob 的速度甚至比传输 ArrayBuffer 更快:

从主线程到工作线程的 Blob 复制时间:1.828125 毫秒

从主线程到工作线程的 ArrayBuffer 传输时间:3.393310546875 毫秒

这表明处理 Blob 实际上是相当便宜的一个强有力的指标。由于它们是不可变的,浏览器似乎聪明到将它们视为引用而不是将潜在的二进制数据链接到这些引用。

这是我在获取 Blob 时拍摄的堆内存快照:

screenshot of the heap memory inspector when fetching binary data as a blob

前两个快照是在使用postMessage将获取到的Blob复制到worker上下文后拍摄的。请注意,这两个堆中都不包括656 MB。
后两个快照是在我使用FileReader实际访问底层数据之后拍摄的,如预期的那样,堆增长了很多。
现在,这就是直接获取ArrayBuffer时发生的情况:

screenshot of the heap memory inspector when fetching binary data as an arraybuffer

这里,由于二进制数据仅在工作线程中传输,因此主线程的堆很小,而工作线程的堆包含整个656 MB的数据,即使在读取此数据之前也是如此。

现在,在SO上查看What is the difference between an ArrayBuffer and a Blob?,可以看到两种结构之间存在许多基本差异,但我没有找到关于是否应该担心在执行上下文之间复制Blob还是ArrayBuffer固有优势的好参考。然而,我的实验表明,复制Blob可能实际上更快,因此我认为更可取。

每个浏览器供应商似乎都有自己的存储和处理 Blob 的方式。我找到了这份Chromium文档,它描述了所有 Blob 都从每个渲染器进程(即标签页上的页面)传输到浏览器进程,这样Chrome甚至可以将Blob卸载到辅助内存中。

是否有人对此有更多见解?如果我可以选择通过网络获取一些大型二进制数据并将其移动到Web Worker,那么我应该选择Blob还是ArrayBuffer


这个问题非常有趣,但是是否有从工作机器中获取文件的限制呢? - Antoine Eskaros
@AntoineRaoulIscaros 是的,对于这种特殊情况,我需要先从主线程获取。 - bitsoverflow
1个回答

5
不,postMessage一个Blob并不昂贵。
一个Blob的克隆步骤如下:

给定值和序列化后的值,它们的序列化步骤是:

  1. 将serialized.[[SnapshotState]]设置为value的快照状态。

  2. 将serialized.[[ByteSequence]]设置为value的底层字节序列。

给定serialized和value,它们的反序列化步骤是:

  1. 将value的快照状态设置为serialized.[[SnapshotState]]。

  2. 将value的底层字节序列设置为serialized.[[ByteSequence]]。

换句话说,并没有复制任何内容,快照状态和字节序列都通过引用传递(即使包装的JS对象不是)。
然而,关于你的整个项目,我不建议在这里使用Blobs,原因有两个:
  1. 获取算法首先以ArrayBuffer的形式进行内部获取。请求Blob会添加一个额外的步骤(这会消耗内存)。
  2. 您可能需要从Worker中读取该Blob,这将添加另一个步骤(这也会消耗内存,因为数据实际上将被复制)。

我明白了!我认为关于1.虽然请求Blob是一个额外的步骤,如https://chromium.googlesource.com/chromium/src/+/master/storage/browser/blob/README.md#summary-terminology所述,浏览器(至少是Chromium)可以决定如何更好地存储数据,以便页面堆可以卸载。关于2.选择Blob的优点是可以像Emscripten的WORKERFS在https://github.com/emscripten-core/emscripten/blob/c56a65e67e296864f2f25e2b05fe155d7ab78c40/src/library_workerfs.js#L136-L140中所做的那样,按块读取Blob。这样做虽然较慢,但似乎更节省内存。 - bitsoverflow
1
我认为,拥有 Blob 的数据以及其中的一部分作为 ArrayBuffer,从内存角度来看并不比始终只拥有数据的单个副本更好。我也不会依赖这篇论文来期望一个浏览器的优化。它似乎相当古老(例如,它似乎没有考虑到 ReadableStreams),而且我很确定事情已经发生了改变。我仍然建议尽可能保持原始状态,并传输所需的唯一 ArrayBuffer。 - Kaiido

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接