先來幾個名詞
直譯器
一行行地邊解釋邊執行
複製程式碼
編譯器
是把原始碼整個編譯成目的碼,執行時不再需要編譯器,直接在支援目的碼的平臺上執行。
複製程式碼
直譯器的利弊
-
直譯器啟動和執行的更快。你不需要等待整個編譯過程完成就可以執行你的程式碼。從第一行開始翻譯,就可以依次繼續執行了。 正是因為這個原因,直譯器看起來更加適合 JavaScript。對於一個 Web 開發人員來講,能夠快速執行程式碼並看到結果是非常重要的。 這就是為什麼最開始的瀏覽器都是用 JavaScript 直譯器的原因。
-
可是當你執行同樣的程式碼一次以上的時候,直譯器的弊處就顯現出來了。比如你執行一個迴圈,那直譯器就不得不一次又一次的進行翻譯,這是一種效率低下的表現。
編譯器的利弊
- 編譯器的問題則恰好相反。 它需要花一些時間對整個原始碼進行編譯,然後生成目標檔案才能在機器上執行。對於有迴圈的程式碼執行的很快,因為它不需要重複的去翻譯每一次迴圈。
Just-in-time 及時編譯(簡稱JIT)
為了解決直譯器的低效問題,後來的瀏覽器把編譯器也引入進來,形成混合模式。
不同的瀏覽器實現這一功能的方式不同,不過其基本思想是一致的。在 JavaScript 引擎中增加一個監視器(也叫分析器)。監視器監控著程式碼的執行情況,記錄程式碼一共執行了多少次、如何執行的等資訊。
起初,監視器監視著所有通過直譯器的程式碼。
如果同一行程式碼執行了幾次,這個程式碼段就被標記成了 “warm”,如果執行了很多次,則被標記成 “hot”。
基線編譯器
如果一段程式碼變成了 “warm”,那麼 JIT 就把它送到編譯器去編譯,並且把編譯結果儲存起來。如果監視器監視到了執行同樣的程式碼和同樣的變數型別,那麼就直接把這個已編譯的版本 push 出來給瀏覽器。
優化編譯器
如果一個程式碼段變得 “very hot”,監視器會把它傳送到優化編譯器中。生成一個更快速和高效的程式碼版本出來,並且儲存之。 更多工作原理介紹請移步 JIT工作原理
JavaScript效能變化曲線
JIT不侷限於JS?
JIT總結
簡而言之 JIT 是什麼呢?它是使 JavaScript 執行更快的一種手段(JIT,內聯快取和隱藏類)之一,通過監視程式碼的執行狀態,把 hot 程式碼(重複執行多次的程式碼)進行優化。通過這種方式,可以使 JavaScript 應用的效能提升很多倍。
為了使執行速度變快,JIT 會增加很多多餘的開銷,這些開銷包括:
- 優化和去優化開銷
- 監視器記錄資訊對記憶體的開銷
- 發生去優化情況時恢復資訊的記錄對記憶體的開銷
各大廠商的方案
- 微軟的 TypeScript 通過為 JS 加入靜態型別檢查來改進 JS 鬆散的語法,提升程式碼健壯性;
- 谷歌的 Dart 則是為瀏覽器引入新的虛擬機器去直接執行 Dart 程式以提升效能;
- 火狐的 asm.js 則是取 JS 的子集,JS 引擎針對 asm.js 做效能優化。
以上嘗試各有優缺點
- TypeScript 只是解決了 JS 語法鬆散的問題,最後還是需要編譯成 JS 去執行,對效能沒有提升;
- Dart 只能在 Chrome 預覽版中執行,無主流瀏覽器支援,用 Dart 開發的人不多;
- asm.js JS語法子集、有很大限制,開發效率低。關於Asm.js的一些基本介紹和它的實現
三大瀏覽器巨頭分別提出了自己的解決方案,互不相容,這違背了 Web 的宗旨; 是技術的規範統一讓 Web走到了今天,因此形成一套新的規範去解決 JS 所面臨的問題迫在眉睫。 於是 WebAssembly 誕生了
WebAssembly(Google , Microsoft , Mozilla , Apple 等幾家大公司合作發起的)
WebAssembly 是一種新的位元組碼格式,主流瀏覽器都已經支援 WebAssembly。 和 JS 需要解釋執行不同的是,WebAssembly 位元組碼和底層機器碼很相似可快速裝載執行,因此效能相對於 JS 解釋執行大大提升。 也就是說 WebAssembly 並不是一門程式語言,而是一份位元組碼標準,需要用高階程式語言編譯出位元組碼放到 WebAssembly 虛擬機器中才能執行.
WebAssembly優點
- 體積小:由於瀏覽器執行時只載入編譯成的位元組碼,一樣的邏輯比用字串描述的 JS 檔案體積要小很多;
- 載入快:由於檔案體積小,再加上無需解釋執行,WebAssembly 能更快的載入並例項化,減少執行前的等待時間;
能編譯成 WebAssembly 位元組碼的高階語言有:
- AssemblyScript:語法和 TypeScript 一致,對前端來說學習成本低。
- c\c++:官方推薦的方式
- Rust:語法複雜、學習成本高,對前端來說可能會不適應。
- Kotlin:語法和 Java、JS 相似,語言學習成本低。
WebAssembly是如何工作的
使用高階語言來編寫WebAssembly模組,將其編譯成.wasm檔案。這些.wasm檔案並不能直接被瀏覽器識別,所以它們需要一種稱為JavaScript膠接程式碼(glue code,用於連線相互不相容的元件,詳見:http://whatis.techtarget.com/definition/glue-code)的東西來載入。
複製程式碼
1.Emscripten編譯器
C到WebAssembly的編譯器,推薦使用Emscripten(https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html),安裝這個工具費時費力費空間。
複製程式碼
有一個線上 C/C++ 轉 wasm 的工具: WasmExplorer
JavaScript膠接代
function loadWebAssembly (path) {
return fetch(path) // 載入檔案
.then(res => res.arrayBuffer()) // 轉成 ArrayBuffer
.then(WebAssembly.instantiate) // 編譯 + 例項化
.then(mod => mod.instance) // 提取生成都模組
}
複製程式碼
其實就是 【載入檔案】->【轉成 buffer】->【編譯】->【例項化】
DEMO C語言到WebAssembly
math.c
int square (int x) {
return x * x;
}
複製程式碼
loader.js
見上節JavaScript膠接代
相容性
各大主流瀏覽器都已經支援!
案例
- 在2017年5月時,白鷺引擎宣佈開始支援 WebAssembly,而利用 WebAssembly,白鷺引擎可以將 HTML 5 程式碼編譯為機器碼執行,讓遊戲執行效能提升 300%。
- 2017年10月底,谷歌開始支援讓 Google Earth 在 Firefox 上執行,其中的關鍵就是使用了 WebAssembly。
- TeaVM 是一個 AOT 編譯器(翻譯器),可將 Java 位元組碼翻譯成 JavaScript 或 WebAssembly 格式。
- Blazor — 讓 .NET 程式碼也能在瀏覽器執行 開源 UI 框架 Blazor 可以讓 .NET 程式碼在瀏覽器環境中執行,而習慣 ASP.NET Razor 語法的開發者,仍可以繼續沿用習慣的開發模式
WebAssembly也許是JS效能的第二個拐點,未來可期
既然編譯好後的機器碼執行速度很快,為啥瀏覽器不嘗試直接支援編譯型語言??
- JavaScript的歷史地位,WebAssembly與其相輔相成.JavaScript巨大的生態系統和友好的語法,WebAssembly接近原生的表現效能。
- WebAssembly 位元組碼非常接近機器碼,可以非常快的被翻譯為對應架構的機器碼,因此 WebAssembly 執行速度和機器碼接近。
- 類比Java 位元組碼