原文标题:Loading WebAssembly modules efficiently原文连接:https://developers.google.com/web/updates/2018/04/loading-wasm(需越墙)javascript
原文做者:Mathias Bynens 译者:西楼听雨 (转载请注明出处)
html
咱们在使用 WebAssembly 的时候,一般的作法都是,先下载一个模块,而后编译它,再进行实例化,最后使用经过 JavaScript 导出(exports)的东西。本文将以一段常见的但不是最优的实现这种作法的代码来开始,接着再讨论几个能够优化的地方,最后以给出一种最简单最高效的方式来结束。
java
提示:像 Emsciptent 这类工具,能够自动帮你生成这种作法的模板代码,因此你不必本身动手编写。本文的目的是考虑到你可能会有须要对 WebAssembly 模块的加载进行精细控制的时候,因此提供下面这些最佳实践,以期给你带来帮助。
下面这段代码的做用就是上面说的这种“下载-编译-实例化”的完整实现,可是是一种欠优化的方式:web
// 不要采用这种方式
(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)
来把一个 response 的 buffer 来转化为一个 module 的。不过这个 API 是同步的,这就意味着在它执行完以前它会一直阻塞主线程。为了抑制对它的使用,Chrome 会在 buffer 的大小超过 4KB 时禁止使用它。若是要避开这个限制,咱们能够改成使用 await WebAssembly.compile(buffer)
:promise
(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 的“4KB buffer 大小”的限制。为了保持一致性以及“保障主线程任什么时候候都不受牵制”的目的,咱们能够改成使用异步的 WebAssembly.instantiate(module)
。bash
(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 的最优的方法。借助于“流式编译(streaming compilation)”,浏览器如今已经能够直接在模块数据还在下载时就开始编译 WebAssembly 模块。因为下载和编译是同时进行的,速度天然更快——特别是在载荷(payload)大的时候(译:即模块的体积大的时候)。服务器
要使用这种优化,咱们须要改 WebAssebly.compile
的使用为WebAssembly.compileStreaming
。这种改变还能够帮咱们避免中间性的 arraybuffer,由于如今咱们传递的直接是 await fetch(url)
返回的 Response 实例了:
app
(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);
})();复制代码
注意:这种方式要求服务器必须对 .wasm 文件作正确的 MIME 类型的配置,方法是发送Content-Type: application/wasm
头。在上一个例子中,这个步骤不是必须的,由于咱们传递的是 response 的 arraybuffer,因此就不会发生对 MIME 类型的检测。
WebAssembly.compileStreaming
API 还支持传入可以解析(resolve)为 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);
})();
复制代码
虽然如此,不过我我的以为把它单独放一行更具可读性。
看到咱们是如何把 response 编译为一个 module,又是如何把它马上实例化的了吗?其实,WebAssembly.instantiate
能够一步到位完成到编译和实例化。WebAssembly.instantiateStreaming
API 固然也能够,并且是流式的:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { module, instance } = await WebAssembly.instantiateStreaming(fetchPromise);
// 稍后建立的另外一个新的实例:
const otherInstance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
复制代码
若是你只须要一个实例的话,保存 module 对象就没有任何意义了,因此代码还能够更一步简化:
// 这就是咱们所推荐的加载 WebAssembley 的方式
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
复制代码
你能够在 WebAssembly Studio 中在线把玩这段代码示例。
总结如下咱们所应用的优化:
祝你玩 WebAssembly 玩的开心!