原文標題:Loading WebAssembly modules efficiently原文連結:https://developers.google.com/web/updates/2018/04/loading-wasm(需越牆)
原文作者:Mathias Bynens 譯者:西樓聽雨 (轉載請註明出處)
我們在使用 WebAssembly 的時候,通常的做法都是,先下載一個模組,然後編譯它,再進行例項化,最後使用通過 JavaScript 匯出(exports)的東西。本文將以一段常見的但不是最優的實現這種做法的程式碼來開始,接著再討論幾個可以優化的地方,最後以給出一種最簡單最高效的方式來結束。
提示:像 Emsciptent 這類工具,可以自動幫你生成這種做法的模板程式碼,所以你沒必要自己動手編寫。本文的目的是考慮到你可能會有需要對 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)
來把一個 response 的 buffer 來轉化為一個 module 的。不過這個 API 是同步的,這就意味著在它執行完之前它會一直阻塞主執行緒。為了抑制對它的使用,Chrome 會在 buffer 的大小超過 4KB 時禁止使用它。如果要避開這個限制,我們可以改為使用 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 的“4KB buffer 大小”的限制。為了保持一致性以及“保障主執行緒任何時候都不受牽制”的目的,我們可以改為使用非同步的 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 的最優的方法。藉助於“流式編譯(streaming compilation)”,瀏覽器現在已經可以直接在模組資料還在下載時就開始編譯 WebAssembly 模組。由於下載和編譯是同時進行的,速度自然更快——特別是在載荷(payload)大的時候(譯:即模組的體積大的時候)。
要使用這種優化,我們需要改 WebAssebly.compile
的使用為WebAssembly.compileStreaming
。這種改變還可以幫我們避免中間性的 arraybuffer,因為現在我們傳遞的直接是 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);
})();複製程式碼
注意:這種方式要求伺服器必須對 .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 玩的開心!