WebAssembly 為什麼這麼快?

WebAssembly 為什麼這麼快?

什麼是 WebAssembly

WebAssembly 是一種使 JavaScript 以外的編程語言編寫的代碼能夠在瀏覽器中運行的技術。所以當人們在討論 WebAssembly 運行之快的時候, 實際上是在和 JavaScript 進行對比。

現在,我不會暗示說這是一個非此即彼的情況——你要麼使用 WebAssembly 或者是 JavaScript。事實上,我們期望開發者能夠在一個應用中同時使用 WebAssembly 與 JavaScript。

但是比較這兩者還是很有用處的,它能夠讓我們瞭解 WebAssembly 帶來的潛在影響。

關於性能的一點歷史

JavaScript 創造於 1995。它並非被設計成一門以快為目的的語言,並且在頭十年,它的確不快。

然後瀏覽器開始變得更有競爭力了。

在2008 年,被人們稱作性能大戰的時期開始了。各種瀏覽器加入了實時編譯器,也被稱為 JIT。在 JavaScript 運行的時候,JIT 能夠分析出其中的模式並基於這些模式讓代碼更快地運行。

這些 JIT 的引入使得 JavaScript 的性能迎來了一個拐點。JS 的執行快了十倍甚至更多。

WebAssembly 为什么这么快?

隨著性能的提示,JavaScript 開始被用於原本誰都預想不到的地方,比如用 Node.js 實現服務端的編程。性能的提升使得 JavaScript 可以被用於解決新的種類的問題。

有了 WebAssembly,或許我們正處於另外一個拐點

WebAssembly 为什么这么快?

那麼,讓我們深入細節,理解 WebAssembly 為什麼這麼快的原因。

WebAssembly 或者 JavaScript 來編程並不是一個二選一的情況。我們並不期望有過多的開發者來編寫全是 WebAssembly 代碼的代碼庫。

所以開發者們並不需要在開發應用時對 WebAssembly 與 JavaScript 之間做出選擇。然而,我們期望看到開發者們能夠將他們的一部分 JavaScript 代碼切換成 WebAssembly

比如,React 團隊可以將它們的協調器代碼(也就是虛擬 DOM)替換成 WebAssembly 版本。而 React 的使用者並不受影響,他們的 app 依舊能夠像往常一樣正常運行,不過他們也能從 WebAssembly 獲益。

像 React 團隊的開發者們切換代碼的原因是因為 WebAssembly 更快。那麼它為何能這麼快呢?

如今 JavaScript 的性能是什麼狀況?

在我們充分理解 JavaScript和 WebAssembly 之間的性能差異之前,我們需要理解 JS 引擎所做的工作。

這張圖片粗略地展示了當今的應用程序的啟動性能是什麼樣。

JS 引擎花在任何這些任務的時間取決於頁面使用的 JavaScript。這張圖並不代表精確的性能參數。它的意義在於提供了一個高級模型來闡述對於同樣的功能,JS 對比 WebAssembly 之間的性能差異。

WebAssembly 为什么这么快?

每一條都顯示了特定任務所花費的時間。

  • 解析 — 將源碼處理成解釋器可以運行的東西所花費的時間。

  • 編譯 + 優化 — 在基線編譯器和優化編譯器中所花費的時間。有一些優化編譯器不再主線程運行,所以沒有包括在這裡。

  • 重優化 — 當 JIT 假定(編譯器對代碼結構的假設,以減少重複編譯)失敗的時候重新調整所花費的時間。包含重新優化和將之前優化過的代碼跳回原來基本代碼。

  • 執行 — 運行代碼所花費的時間。

  • 垃圾回收 — 清理內存所花費的時間。

值得強調的是:這些任務並非在離散的塊或者特定的序列裡發生。 相反,它們是交錯的。解析一小段,然後執行一小段,然後編譯,然後又解析更多的代碼,然後再執行更多的代碼,諸如此類…

這種分離與早期的 JavaScript 的性能相比帶來了很大的改進,早期的看起來像是這樣:

WebAssembly 为什么这么快?

最開始,只有一個解釋器來運行 JavaScript,它的執行速度是非常慢的。當 JIT 被引入之後,它徹底地提升了執行的時間。

監測和編譯代碼的開銷是折中的。如果 JavaScript 開發者一直用同樣的方式編寫 JavaScript,那麼,解析和編譯的時間就很短。但是性能的提升導致開發者們構建大型的 JavaScript 應用。

這意味著依然還有提升的空間。

WebAssembly 要如何比較?

這裡有一個 WebAssembly 對一個典型的 web 應用的對比的估測。

WebAssembly 为什么这么快?

不同的瀏覽器之間處理這些解析有著輕微的不同,在這裡我以 SpiderMonkey 作為模型。

1. 抓取

這個過程並沒有顯示在圖中,不過從服務器中抓取文件本來就是需要佔用一些時間的一件事。

因為 WebAssembly 比 JavaScript 更為壓縮,因此抓取速度也更快。雖然壓縮算法可以顯著地減少 JavaScript 打包文件的體積,使用壓縮的二進制表示的 WebAssembly 仍然更勝一籌。

這意味著在客戶端和服務器之間傳輸所花費的時間更少,特別是在緩慢的網絡連接的情況下。

2. 解析
一旦數據到達了瀏覽器,JavaScript 源碼開始解析成一個抽象語法樹(AST)。

瀏覽器經常惰性地做這件事,因為它值解析一開始它需要的東西,並且為沒有被調用的函數只創建存根。

然後 AST 會被轉化成特定 JS 引擎的一箇中間表示(叫做字節碼)。

相反,In contrast, WebAssembly 不需要經歷著個轉換過程,因為它本身就已經是中間表示了。它只需要被解碼然後驗證以保證沒有任何錯誤在裡面。

WebAssembly 为什么这么快?

3. 編譯 + 優化

JavaScript 是在代碼執行的時候編譯的。取決於運行時所需要的類型,同樣的代碼的不同版本可能需要多次編譯。

不同瀏覽器處理Different browsers handle compiling WebAssembly 的編譯也不同。一些瀏覽器在開始執行 WebAssembly 之前對它做一個基線編譯,其他的則使用 JIT 。

無論哪種方式,WebAssembly 起始更接近機器碼。打個比方,類型是程序的一部分。它更快的原因有:

  1. 編譯器在開始編譯優化的代碼之前並不需要花時間去觀察當前正在使用的是什麼類型。

  2. 編譯器不需要根據觀察到的不同類型來對同樣的代碼編譯不同的版本。

  3. 在 LLVM 時已經提前做了許多的優化。所以編譯和優化的工作就相對更少了。

WebAssembly 为什么这么快?

4. 重優化

有時候,JIT 會扔出一些優化過的代碼然後嘗試重新優化。

這個過程發生在當 JIT 依據正在運行的代碼做出的假定是正確的時候。比如,去優化發生在循環裡的變量和它先前迭代的時候不一樣,或者是當一個新的函數被插入到原型鏈當中。

對去優化來說有兩種成本。首先,它需要將優化過的代碼退回基本版本。其次,如果某個函數仍然被多次調用,那麼 JIT 可能會將它重新送至優化編譯器,所以這就有了二次編譯的成本。

在 WebAssembly 當中,類型是顯式的,所以 JIT 不需要根據運行時收集的數據對類型做假設。 這意味著它不需要經歷重優化的循環。

WebAssembly 为什么这么快?

5. 執行
書寫高性能的 JavaScript 是可行的。為了達到這個目的,你需要了解 JIT 執行的優化。比如,你需要知道如何編寫能夠讓編譯器能輕易地類型特化的代碼。

而,大多數開發者並不知道 JIT 的內部原理。就算那些開發者瞭解 JIT 的內部原理,仍然可能達不到目的。人們使用的一些使得代碼可讀性更強的編碼模式(比如將通用任務抽象成為可以處理不同的數據類型的函數)反而在編譯器優化代碼的時候給編譯器造成了麻煩。

此外,JIT 使用的優化手段在不同瀏覽器中是不同的,所以正對某個瀏覽器內部的原理的編碼可能會造成在其他瀏覽器內的性能下降。

正因為如此,一般執行 WebAssembly 中的代碼通常來說要更快。許多 JIT 針對 JavaScript 的優化(比如類型特化)對 WebAssembly 來說是完全沒有必要的。

除此之外, WebAssembly 被設計成編譯器的目標。這意味著它被設計成為編譯器能夠生成的,而不是人類程序員可以書寫的。

由於人類程序員不需要直接對它編程,WebAssembly 可以提供一系列的對機器更加理想的指令。取決於你的代碼的具體有著什麼樣的目的,這些指令的運行速度從 10% 到 800% 更快。

WebAssembly 为什么这么快?

6. 垃圾回收
在 JavaScript 當中,開發者不必擔心變量再需要的時候去內存中清理它們。JS 引擎自動地使用了叫做垃圾回收器的東西來處理它們。

如果你需要可預測的性能,那麼這樣可能會出現一些問題。你無法控制什麼時候垃圾回收器該工作,它可能會在一些不恰當的時機出現。大多數瀏覽器都很擅長調度它,但是它仍然有一些開銷,它會阻礙代碼的執行。

至少目前來說,WebAssembly 完全不支持垃圾回收。內存需要手動管理(就像 C 和 C++ 那樣)。那麼這樣會使得編程對程序員來說更加困難,不過它確實能夠使得性能更加一致。

WebAssembly 为什么这么快?

總結

WebAssembly 在很多方面比 JavaScript 更快的原因是:

  • 抓取 WebAssembly 比 JavaScript 花費的時間更少,哪怕當它們都被壓縮過。

  • 編碼 WebAssembly 比解析 JavaScript 所花費的時間更少。

  • WebAssembly 比 JavaScript 更加接近機器碼而且在服務端就已經經過了優化,所以它編譯和優化需要的時間更少。

  • WebAssembly 不需要重優化,因為它有明確的類型以及內置的額外信息,所以 JS 引擎不需要像優化 JavaScript 那樣對它進行推測。

  • 執行階段花費的時間更少,開發者不必為了寫出性能一致性更高的代碼而去了解一些編譯器的技巧和陷阱。而且 WebAssembly 的一系列的只能對機器來說更加理想。

  • 不需要垃圾回收機制,因為內存都是手動管理的。

這就是為什麼在很多例子中,對於同樣的任務,WebAssembly 的表現要比 JavaScript 更好。

本文是譯文,原文鏈接是:https://hacks.mozilla.org/2017/02/what-makes-webassembly-fast/

「一個有溫度的前端號」

長按識別二維碼關注

WebAssembly 为什么这么快?


深入理解虛擬 DOM,它真的不快


尤大多倫多演講:Vue 3.0 預覽


說說前端未來幾年的發展方向



分享到:


相關文章: