谷歌:如何以最高效的方式加載 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 玩的開心!

相關文章
相關標籤/搜索