作者:Alon Zakai
編譯:鬍子大哈
翻譯原文:huziketang.com/blog/posts/…
英文原文:Why WebAssembly is Faster Than asm.js
轉載請註明出處,保留原文連結以及作者資訊
本文作者:Alon Zakai
WebAssembly 是為 Web 而設計的、可以生成瀏覽器可執行的二進位制檔案的程式語言。並且於2017 年 2 月 28 日,四個主要的瀏覽器一致同意宣佈 WebAssembly 的 MVP 版本已經完成,即將推出一個瀏覽器可以搭載的穩定版本。WebAssembly 的一個主要目標就是變快。本文將給出一些它如何變快的技術細節。
當然,“快”是相對的概念。相比於 JavaScript 和其他動態語言,WebAssembly 的快主要是因為它的靜態型別特性和方便優化特性。WebAssembly 意在速度上能夠達到和本地執行一樣快,其實 asm.js 已經比較接近這一目標了,但是 WebAssembly 要進一步縮短和本地執行速度之間的差距。因此本文著重介紹為什麼 WebAssembly 比 asm.js 更快。
在開始介紹之前,先做一些說明:新的技術總是有一些還沒來得及優化的情況,所以目前來說,並不是所有情況下 WebAssembly 都是最快的。本文主要表達的是 WebAssembly 為什麼應該是更快的。對於它還不是那麼快的一些情況,也是未來需要 fix 的問題。
那麼有了這些標準以後,我們來介紹為什麼 WebAssembly 更快。
1. 啟動
WebAssembly 設計之初就定位在:體積更小、下載更快、解析更快,這樣即使應用於一個大型的 web 應用上啟動也會很快。
JavaScript 程式碼通過 gzip 進行壓縮,與原生程式碼相比它已經壓縮了很多,想要進一步縮小它的體積確實不是一件容易的事情。但是通過對 WebAssembly 檔案大小的精心設計(LEB128 指標),二進位制格式的 WebAssembly 的檔案大小要比壓縮後的 JavaScript 檔案更小。通常來講,要比 gzip 壓縮後的小 10% - 20%。
WebAssembly 在解析過程有更大的進步:它的解析速度要比 JavaScript 快一個數量級。這得益於 WebAssembly 的二進位制格式就是為更適合解析而設計的。同時 WebAssembly 的解析和優化函式也更容易實現並行,這一特點在多核機器上的體現更好。
影響啟動時間的因素除了下載和解析以外還有很多,比如程式碼的 VM 完全優化、執行之前下載所必須的額外檔案等。但是下載和解析步驟是無論如何都不可避免的步驟,因此要儘可能地對它們進行優化。不論是對於瀏覽器還是 app,其他的影響因素都有辦法避免或者緩和它們的影響(例如程式碼的完全優化,可以通過 WebAssembly 的基線編譯器或直譯器來避免)。
2. CPU 特性
使得 asm.js 這麼快的技巧之一是利用好 CPU 特性。JavaScript 數字型別都是 double 型的,在 asm.js 中加法操作後會有一個按位與的操作,這使得 double 加法這個操作,在邏輯上和 CPU 做簡單的 int 加法(CPU 做簡單 int 加操作很快)是等價的。而 asm.js 巧妙地通過這種方式使得 VM 可以更加有效地利用 CPU。
但是有一些 JavaScript 中表達的東西,asm.js 是表達不了的。
WebAssembly 則不受 JavaScript 的約束,我們一起來看一下更多的可利用的 CPU 特性:
- 64 位整型。基於 64 位整型的操作速度可以快 4 倍。例如可以增加雜湊演算法和加密演算法的速度。
- 載入和儲存偏移量。它的用處特別廣泛,基本上所有包含域的記憶體物件都涉及到偏移量的問題(如 C 語言中 struct 等)。
- 非對齊載入和儲存。這避免了 asm.js 所需要的 mask(asm.js 為 Typed Array 相容而做的操作),同時這在幾乎所有的載入和儲存問題都用得上。
- 多種 CPU 指令,例如 popcount,copysign 等。每一個指令都有助於某種具體使用場景(例如,popcount 在密碼分析這個場景下很有用)。
在每一種具體場景下到底能起到多大作用,要依賴於如何使用上面提到的特性。和正常使用 asm.js 相比,我們的統計是大概比 asm.js 提升了 5% 的速率。未來還有更多可挖掘的 CPU 特性來提高速率,例如 SIMD。
3. 工具鏈改進
WebAssembly 是編譯器生成的主要目標,所以它的執行主要包含兩個部分:生成它的編譯器(工具鏈端)和執行它的虛擬機器(瀏覽器端)。優良的效能全都依賴於這兩部分。
對於 asm.js,情況也類似,並且 Emscripten 做了一系列對工具鏈的優化,還做了執行 LLVM 的優化器和 Emscripten 的 asm.js 優化器。對 WebAssembly 的優化都是在這些的基礎上來設計的,並且同時還加入了一些專門針對 WebAssembly 的改進。我們自己在學習 asm.js 的過程對我們改善 WebAssembly 很有幫助,尤其體現在一下幾個方面:
- 我們使用 Binaryen WebAssembly 優化器來代替 Emscripten asm.js 優化器,前者是專門為速度而設計,其速度的提升是以更多優化步驟為代價的。例如在優化的過程中會移除重複函式,這樣會使 C++ 編譯程式碼整體減小 5% 左右。
- 對冗餘、複雜的控制流進行更好的優化,這可以提升 Relooper 演算法的效率,對於編譯解釋型別迴圈也很有幫助。
- Binaryen 優化器是在試驗中不斷設計和完善的,通過超級優化器做的實驗也會帶來各種情況的微妙改進——我想 asm.js 也做過同樣的事情。
總的來說,這些工具鏈的改進,在 asm.js 的基礎上進一步提升了 WebAssembly 的速度(Box2D 速度評測中,WebAssembly 的速度分別提升了 5% 和 7%)。
可預見的優良效能
asm.js 的速度很接近本地執行的速度了,但是它不可能在所有的瀏覽器中都達到同樣的標準。因為在使用 asm.js 的過程中,一些人嘗試用一種方法來優化 asm.js,而另一些人用另一種方法優化,這導致了不同人有不同的版本和結果。雖然隨著時間的推移,大家對於 asm.js 也達成了某種共識,但是對於 asm.js 來講,本質問題是它還是沒有一個統一的標準。它只是一個由一個廠商推出的,非標準的 JavaScript 子集而已,而它的使用者根據自己的偏好和習慣來使用它。
WebAssembly 則不同,它是由幾大主要的瀏覽器廠商共同設計的。與 JavaScript相比,JavaScript 只能通過一些創新方法或者 asm.js 這種方法來提高速度,而不論哪種方法都不可能適用於所有瀏覽器。WebAssembly 的優化方案則得到了大多數廠商的認可。對於 WebAssembly 來講,對於不同的 VM 依舊有很大的提升空間(如 AOT、JIT 有不同編譯方式)。不過能夠基本斷定的是,可以應用於整個網路的優良效能是指日可期的。
想了解更多關於 WebAssembly 的知識,請移步下面 WebAssembly 系列文章。
背景知識:
- WebAssembly 系列(一)生動形象地介紹 WebAssembly
- WebAssembly 系列(二)JavaScript Just-in-time (JIT) 工作原理
- WebAssembly 系列(三)編譯器如何生成彙編
當前 WebAssembly 的狀況
WebAssembly 的未來
轉載請註明出處,保留原文連結以及作者資訊
歡迎大家關注我的前端大哈 - 知乎專欄,定期釋出高質量前端文章。
我最近正在寫一本《React.js 小書》,對 React.js 感興趣的童鞋,歡迎指點。