原文標題: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 玩的開心!