有效率地載入 WebAssembly 模組

使用 WebAssembly 時,您通常會想要下載、編譯、對模組執行個體化,然後使用任何以 JavaScript 匯出的內容。這篇文章將說明我們建議的做法,協助你提升效率。

Mathias Bynens
Mathias Bynens

使用 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,代表在主執行緒完成前封鎖主執行緒。如果不希望使用 Google 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),其擁有相同的 4 KB 緩衝區 Chrome 的檔案大小限制。為保持一致性,目的是保留主執行緒 免費,我們可以使用非同步 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() 就會
下載最後一個位元組後,幾乎會立即完成編譯。

如要啟用這項最佳化功能,請使用 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 的承諾 執行個體。如果未在程式碼中需要 response,可以傳遞承諾 由 fetch 直接傳回,而不明確 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!