這是專門探索 JavaScript 及其所構建的組件的系列文章的第6篇。
推薦免費:JavaScript
這次將講解 WebAssembly 是如何工作的,更重要的是,它是如何在性能方面與JavaScript進行比較的:加載時間、執行速度、垃圾收集、內存使用、API開放平臺、調試、多線程和可移植性。
首先,讓我們看看WebAssembly做什么
首先,我們有必要了解一下asm.js。2012年,Mozilla 的工程師 Alon Zakai 在研究 LLVM 編譯器時突發奇想:許多 3D 游戲都是用 C / C++ 語言寫的,如果能將 C / C++ 語言編譯成 JavaScript 代碼,它們不就能在瀏覽器里運行了嗎?眾所周知,JavaScript 的基本語法與 C 語言高度相似。于是,他開始研究怎么才能實現這個目標,為此專門做了一個編譯器項目 Emscripten。這個編譯器可以將 C / C++ 代碼編譯成 JS 代碼,但不是普通的 JS,而是一種叫做 asm.js 的 JavaScript 變體,性能差不多是原生代碼的50%。
之后Google開發了Portable Native Client,也是一種能讓瀏覽器運行C/C++代碼的技術。 后來可能是因為彼此之間有共同的更高追求,Google, Microsoft, Mozilla, Apple等幾家大公司一起合作開發了一個面向Web的通用二進制和文本格式的項目,那就是WebAssembly。asm.js 與 WebAssembly 功能基本一致,就是轉出來的代碼不一樣:asm.js 是文本,WebAssembly 是二進制字節碼,因此運行速度更快、體積更小。
WebAssembly(又稱 wasm) 是一種新的字節碼格式,主流瀏覽器都已經支持 WebAssembly。 和 JS 需要解釋執行不同的是,WebAssembly 字節碼和底層機器碼很相似可快速裝載運行,因此性能相對于 JS 解釋執行大大提升。 也就是說 WebAssembly 并不是一門編程語言,而是一份字節碼標準,需要用高級編程語言編譯出字節碼放到 WebAssembly 虛擬機中才能運行, 瀏覽器廠商需要做的就是根據 WebAssembly 規范實現虛擬機。
WebAssembly 加載時間
WebAssembly 在瀏覽器中加載速度更快,因為只有已經編譯好的 wasm 文件需要通過internet傳輸。wasm 是一種低級匯編語言,具有非常簡潔的二進制格式。
WebAssembly 執行速度
如今 Wasm 運行速度只比原生代碼慢 20%,這是一個令人驚喜的結果。它是這樣的一種格式,會被編譯進沙箱環境中且在大量的約束條件下運行以保證沒有任何安全漏洞或者使之強化。和真正的原生代碼比較,執行速度的下降微乎其微。更重要的是,未來將會更加快速。
更好的是,它與瀏覽器無關——所有主要引擎都增加了對 WebAssembly的支持,且執行速度相差無幾。
為了理解與JavaScript相比WebAssembly的執行速度有多快,應該首先閱讀關于JavaScript引擎如何工作的文章。
讓我們快速瀏覽下 V8 的運行機制:
在左邊,是一些JavaScript源代碼,包含JavaScript函數。首先需要解析它,以便將所有字符串轉換為標記并生成抽象語法樹(AST)。AST 是JavaScript程序邏輯結構在內存中的表示形式。一旦生成了 AST,V8 直接進入到機器碼階段。其后遍歷樹,生成機器碼,就得到了編譯好的函數,在這個過程中是沒有提高遍歷速度的。
現在,讓我們看看V8管道在下一階段的工作:
現在有了V8 的新的優化編譯器 (TurboFan), 當 JavaScript應用程序在運行時,很多代碼都在 V8 中運行。TurboFan 監測是否有代碼運行緩慢,是否存在性能瓶頸和熱點(內存使用過高的地方),以便對其進行優化。它把以上監視得到的代碼推向后端即優化過的即時編譯器,該編譯器把消耗大量 CPU 資源的函數轉換為性能更優的代碼。
它解決了性能的問題,但這種處理方式有個缺點,分析代碼和決定優化哪些內容的過程也會消耗CPU,這意味著更高的耗電量,特別是在移動設備上。
但是,wasm 并不需要以上的全部步驟-如下所示是它被插入到執行過程示意圖:
在編譯階段,WebAssembly 不需要被轉換,因為它已經是字節碼了。總之,以上的解析不在需要,你擁有優化后的二進制代碼可以直接插入到后端(即時編譯器)并生成機器碼。編譯器在前端已經完成了所有的代碼優化工作。
由于跳過了編譯過程中的不少步驟,這使得 wasm 的執行更加高效。
WebAssembly 內存模型
例如,編譯 成WebAssembly 的c++ 程序的內存是一個連續的內存塊,其中沒有“漏洞”。wasm 有助于提高安全性的一個特性是執行堆棧與線性內存分離的概念。在 c++ 程序中,如果有一個堆,從堆的底部進行分配,然后從其頂部獲得內存來增加內存堆棧的大小。你可以獲得一個指針然后在堆棧內存中遍歷以操作你不應該接觸到的變量。
這是大多數可疑軟件可以利用的漏洞。
WebAssembly采用了完全不同的內在模式。執行堆棧與 WebAssembly 程序本身是分開的,因此無法在其中修改和更改諸如變量的值。同樣,這些函數使用整數偏移量,而不是指針。函數指向一個間接函數表。之后,這些直接的計算出的數字進入模塊中的函數。通過這種方式構建的,可以同時加載多個 wasm 模塊,偏移所有索引且每個模塊都運行良好。