谷歌:如何以最高效的方式加载 WebAssembly

原文标题: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)大的时候(译:即模块的体积大的时候)。服务器

When the download time is
longer than the compilation time of the WebAssembly module, then WebAssembly.compileStreaming()
finishes compilation almost immediately after the last bytes are downloaded.

要使用这种优化,咱们须要改 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 中在线把玩这段代码示例

总结如下咱们所应用的优化:

  • 使用异步 API 来防止主线程阻塞
  • 使用流式 API 来更快地编译和实例化 WebAssembly 模块
  • 不写不须要代码

祝你玩 WebAssembly 玩的开心!

相关文章
相关标签/搜索