使用 WebAssembly 时,您通常需要下载一个模块,对其进行编译,对其进行实例化,然后使用它以 JavaScript 格式导出的任何内容。这篇博文介绍了为实现最优效率而推荐的方法。
使用 WebAssembly 时,您通常需要下载一个模块,对其进行编译,对其进行实例化,然后使用它以 JavaScript 格式导出的任何内容。这篇博文的开头就是一个常见但不太理想的代码段,可以实现这一点,讨论了几种可能的优化,最后介绍了从 JavaScript 运行 WebAssembly 的最简单、最高效的方法。
此代码段会执行完整的下载-编译-实例化舞蹈,但方式并不理想:
不要使用!
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = new WebAssembly.Module(buffer);
const instance = new WebAssembly.Instance(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
请注意我们如何使用 new WebAssembly.Module(buffer)
将响应缓冲区转换为模块。这是一个同步 API,这意味着它会阻塞主线程,直到主线程完成为止。如果不建议使用它,Chrome 会停用 WebAssembly.Module
大小超过 4 KB 的缓冲区。为了解决大小限制,我们可以改用 await WebAssembly.compile(buffer)
:
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = new WebAssembly.Instance(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
await WebAssembly.compile(buffer)
仍然不是最佳方法,但我们稍后会谈到这一点。
修改后的代码段中几乎每项操作现在都是异步的,因为 await
的使用明确了。唯一的例外是 new WebAssembly.Instance(module)
,它在 Chrome 中具有相同的 4 KB 缓冲区大小限制。为了保持一致性以及为了保持主线程处于空闲状态,我们可以使用异步 WebAssembly.instantiate(module)
。
(async () => {
const response = await fetch('fibonacci.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
我们再来看看我之前提示的 compile
优化。使用流式编译时,浏览器已经在模块字节仍在下载期间开始编译 WebAssembly 模块。由于下载和编译是并行进行的,因此速度会更快,尤其是对于大型载荷。
![当下载时间长于 WebAssembly 模块的编译时间时,WebAssembly.compileStreaming() 几乎会在最后字节下载完毕后立即完成编译。](https://web.developers.google.cn/static/articles/loading-wasm/image/when-download-is-longer-48c21d175617c.png?authuser=5&hl=sv)
如需启用此优化,请使用 WebAssembly.compileStreaming
而不是 WebAssembly.compile
。这项更改还让我们能够去除中间数组缓冲区,因为我们现在可以直接传递 await fetch(url)
返回的 Response
实例。
(async () => {
const response = await fetch('fibonacci.wasm');
const module = await WebAssembly.compileStreaming(response);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
WebAssembly.compileStreaming
API 还接受解析为 Response
实例的 promise。如果您不需要在代码中的其他位置使用 response
,则可以直接传递 fetch
返回的 promise,而无需明确对其结果执行 await
:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const module = await WebAssembly.compileStreaming(fetchPromise);
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
如果您在其他地方也不需要 fetch
结果,甚至可以直接传递结果:
(async () => {
const module = await WebAssembly.compileStreaming(
fetch('fibonacci.wasm'));
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
不过,我个人发现,将其单独放在一行中更便于阅读。
看看我们如何将响应编译到模块中,并立即将其实例化?事实证明,WebAssembly.instantiate
可以一键进行编译和实例化。WebAssembly.instantiateStreaming
API 以流式传输的方式执行此操作:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { module, instance } = await WebAssembly.instantiateStreaming(fetchPromise);
// To create a new instance later:
const otherInstance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
如果您只需要一个实例,没有必要保留 module
对象,进一步简化代码:
// This is our recommended way of loading WebAssembly.
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
我们采取的优化措施总结如下:
- 使用异步 API 以免阻塞主线程
- 使用流式传输 API 更快地编译和实例化 WebAssembly 模块
- 不要编写您不需要的代码
尽情享受 WebAssembly 吧!