使用 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,这意味着它会阻塞主线程,直到它完成。为避免使用 WebAssembly.Module
,Chrome 会针对大于 4 KB 的缓冲区停用 WebAssembly.Module
。为了解决大小限制问题,我们可以改用 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.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!