将一个大的 blob 复制给一个 worker 是不是很昂贵?
Is copying a large blob over to a worker expensive?
使用 Fetch API 我能够对大量二进制数据资产(比如超过 500 MB)发出网络请求,然后将 Response
转换为 Blob
或 ArrayBuffer
.
之后,我可以执行 worker.postMessage
并让标准结构化克隆算法将 Blob
复制到 Web Worker 或将 ArrayBuffer
传输到 worker 上下文(使实际上不再可从主线程获得)。
起初,似乎以 ArrayBuffer
形式获取数据会更可取,因为 Blob
不可转移,因此需要复制过来。但是,blob 是不可变的,因此,浏览器似乎没有将其存储在与页面关联的 JS 堆中,而是存储在专用的 blob 存储 space 中,因此,最终被复制到worker 上下文只是一个参考。
我准备了一个演示来尝试两种方法之间的区别:https://blobvsab.vercel.app/。我正在使用这两种方法获取 656 MB 的二进制数据。
我在本地测试中观察到的一些有趣的事情是,复制 Blob 甚至比传输 ArrayBuffer
:
Blob
从主线程复制到worker的时间:1.828125 ms
ArrayBuffer
从主线程到worker的传输时间:3.393310546875 ms
这是一个强有力的指标,表明处理 Blob 实际上非常便宜。由于它们是不可变的,浏览器似乎足够聪明,可以将它们视为参考,而不是将覆盖的二进制数据链接到这些参考。
以下是我作为 Blob
:
获取时拍摄的堆内存快照
前两个快照是在使用 postMessage
将提取结果 Blob
复制到工作上下文后拍摄的。请注意,这些堆都不包含 656 MB。
后两个快照是在我使用 FileReader
实际访问底层数据后拍摄的,正如预期的那样,堆增长了很多。
现在,这就是直接作为 ArrayBuffer
:
获取时发生的情况
在这里,由于二进制数据只是通过工作线程传输的,因此主线程的堆很小,但工作堆包含全部 656 MB,甚至在读取此数据之前。
现在,环顾 SO 我发现 What is the difference between an ArrayBuffer and a Blob? 提到了两种结构之间的许多潜在差异,但我还没有找到关于是否应该担心复制 Blob
在执行上下文与 ArrayBuffer
的内在优势之间,它们是可转移的。但是,我的实验表明复制 Blob
实际上可能更快,因此我认为更可取。
这似乎取决于每个浏览器供应商如何存储和处理 Blob
。我发现 this Chromium documentation 描述所有 Blobs
都从每个渲染器进程(即选项卡上的页面)传输到浏览器进程,这样 Chrome 甚至可以卸载 Blob
到辅助内存(如果需要)。
有没有人对这一切有更多的见解?如果我可以选择通过网络获取一些大型二进制数据并将其移动到 Web Worker,我应该更喜欢 Blob
还是 ArrayBuffer
?
不,postMessage 一个 Blob 一点都不贵。
Blob 的 cloning steps 是
Their serialization steps, given value and serialized, are:
Set serialized.[[SnapshotState]] to value’s snapshot state.
Set serialized.[[ByteSequence]] to value’s underlying byte sequence.
Their deserialization step, given serialized and value, are:
Set value’s snapshot state to serialized.[[SnapshotState]].
Set value’s underlying byte sequence to serialized.[[ByteSequence]].
换句话说,没有复制任何东西,快照状态和字节序列都是通过引用传递的,(即使包装 JS 对象不是)。
但是关于您的整个项目,我不建议在这里使用 Blob,原因有二:
- 获取算法首先在内部作为 ArrayBuffer 获取。请求 Blob 会在那里添加一个额外的步骤(这会消耗内存)。
- 您可能需要从 Worker 读取该 Blob,再添加一个步骤(这也会消耗内存,因为此处数据实际上会被复制)。
使用 Fetch API 我能够对大量二进制数据资产(比如超过 500 MB)发出网络请求,然后将 Response
转换为 Blob
或 ArrayBuffer
.
之后,我可以执行 worker.postMessage
并让标准结构化克隆算法将 Blob
复制到 Web Worker 或将 ArrayBuffer
传输到 worker 上下文(使实际上不再可从主线程获得)。
起初,似乎以 ArrayBuffer
形式获取数据会更可取,因为 Blob
不可转移,因此需要复制过来。但是,blob 是不可变的,因此,浏览器似乎没有将其存储在与页面关联的 JS 堆中,而是存储在专用的 blob 存储 space 中,因此,最终被复制到worker 上下文只是一个参考。
我准备了一个演示来尝试两种方法之间的区别:https://blobvsab.vercel.app/。我正在使用这两种方法获取 656 MB 的二进制数据。
我在本地测试中观察到的一些有趣的事情是,复制 Blob 甚至比传输 ArrayBuffer
:
Blob
从主线程复制到worker的时间:1.828125 ms
ArrayBuffer
从主线程到worker的传输时间:3.393310546875 ms
这是一个强有力的指标,表明处理 Blob 实际上非常便宜。由于它们是不可变的,浏览器似乎足够聪明,可以将它们视为参考,而不是将覆盖的二进制数据链接到这些参考。
以下是我作为 Blob
:
前两个快照是在使用 postMessage
将提取结果 Blob
复制到工作上下文后拍摄的。请注意,这些堆都不包含 656 MB。
后两个快照是在我使用 FileReader
实际访问底层数据后拍摄的,正如预期的那样,堆增长了很多。
现在,这就是直接作为 ArrayBuffer
:
在这里,由于二进制数据只是通过工作线程传输的,因此主线程的堆很小,但工作堆包含全部 656 MB,甚至在读取此数据之前。
现在,环顾 SO 我发现 What is the difference between an ArrayBuffer and a Blob? 提到了两种结构之间的许多潜在差异,但我还没有找到关于是否应该担心复制 Blob
在执行上下文与 ArrayBuffer
的内在优势之间,它们是可转移的。但是,我的实验表明复制 Blob
实际上可能更快,因此我认为更可取。
这似乎取决于每个浏览器供应商如何存储和处理 Blob
。我发现 this Chromium documentation 描述所有 Blobs
都从每个渲染器进程(即选项卡上的页面)传输到浏览器进程,这样 Chrome 甚至可以卸载 Blob
到辅助内存(如果需要)。
有没有人对这一切有更多的见解?如果我可以选择通过网络获取一些大型二进制数据并将其移动到 Web Worker,我应该更喜欢 Blob
还是 ArrayBuffer
?
不,postMessage 一个 Blob 一点都不贵。
Blob 的 cloning steps 是
Their serialization steps, given value and serialized, are:
Set serialized.[[SnapshotState]] to value’s snapshot state.
Set serialized.[[ByteSequence]] to value’s underlying byte sequence.
Their deserialization step, given serialized and value, are:
Set value’s snapshot state to serialized.[[SnapshotState]].
Set value’s underlying byte sequence to serialized.[[ByteSequence]].
换句话说,没有复制任何东西,快照状态和字节序列都是通过引用传递的,(即使包装 JS 对象不是)。
但是关于您的整个项目,我不建议在这里使用 Blob,原因有二:
- 获取算法首先在内部作为 ArrayBuffer 获取。请求 Blob 会在那里添加一个额外的步骤(这会消耗内存)。
- 您可能需要从 Worker 读取该 Blob,再添加一个步骤(这也会消耗内存,因为此处数据实际上会被复制)。