以往載入大型 WebAssembly 模組是序列式的:瀏覽器先把整個檔案下載到 ArrayBuffer,交給 JavaScript 引擎,然後才開始編譯。在快速連線下,網路不再是瓶頸;解碼與編譯的 CPU 時間才是,而這些工作全都在下載完成後才發生,且主執行緒會暫停。

顯而易見的修正方案各有代價。單一最佳化編譯器能產生快速的程式碼,但啟動很慢。JavaScript 引擎在執行時才延遲編譯,這避免了工作,但編譯仍留在主執行緒上,並且要等解析完成。而且似乎無法在檔案抵達前編譯,因為舊 API 只接受完整的 ArrayBuffer。

Firefox 58 從兩端著手。串流編譯透過串流 API 將每個下載的區塊直接送入引擎,而且由於 wasm 模組的程式碼區段位於資料區段之前,因此當數 MB 的資料仍在傳輸時,編譯就能開始。新的分層編譯器加入了一個基準層級,其建構程式碼的速度比最佳化編譯器快 10-15 倍,而輸出執行速度只慢約 2 倍,因此程式碼幾乎立即開始執行;背景執行緒隨後用最佳化編譯器重新編譯整個模組並熱替換。兩個編譯器都將工作按函式拆分到多個核心上。

由於編譯吞吐量現在超過網路吞吐量,程式碼幾乎在最後一個封包抵達時就準備好了。Mozilla 將這項變革描述為讓 WebAssembly 載入起來像解碼圖片,而不是解析 JavaScript:沒有主執行緒編譯暫停,也不必等待整個載荷。

在位元組抵達時將其串流送入快速的基準層級,立即執行該程式碼,並熱替換為背景最佳化的程式碼

編譯第一個封包將下載時間隱藏在 CPU 工作中,而不是將下載與編譯序列化。

編譯速度快 10-15 倍的基準層級將啟動變成快速啟動;速度約慢 2 倍的程式碼在最佳化進行時執行。

由於 wasm 函式可獨立編譯,工作會分散到多個核心,而不是在單一執行緒上排隊。

將模組的程式碼放在資料之前,讓串流能在載荷位元組仍在抵達時編譯昂貴的部分。

每項變更都縮短啟動時間,而不改變最終執行的機器碼,因此峰值效能不受影響。

當瓶頸步驟需要等待完整輸入時,從第一個區塊就開始,並先交付便宜的版本;在背景升級它,而不是阻塞等待

Firefox 58 於 2018 年 1 月推出串流與分層編譯器;發布說明在 Hacker News 上引發了 912 分的討論串。Mozilla 的測試頁面讓使用者測量自己裝置上的編譯速度,團隊表示已奠定基礎,可將編譯後的機器碼快取在 HTTP 快取中,延伸同版本推出的 JavaScript 位元組碼快取。

追溯依據

參考來源

  1. Making WebAssembly even faster: Firefox's new streaming and tiering compiler Mozilla Hacks · hacks.mozilla.org ·
  2. Hacker News discussion: Making WebAssembly even faster Hacker News · news.ycombinator.com ·