拜瀏覽器大戰所賜,主流瀏覽器的 JS 引擎已經引入了各種優化技術,不時出現的某某瀏覽器效能大幅提升的新聞也讓前端同學們信心倍增。那麼 JS 這門語言本身是否已經達到了接近原生的水平呢?
思路
我們知道,即便引入了 JIT 等更高階的編譯技術,當今的 JavaScript 本質上也是一門指令碼語言。不要說對比 C / C++ 這樣能編譯到原生機器碼的語言,即便和使用了位元組碼的 Java 比起來,JavaScript 的執行時在原理上也沒有優勢。那麼問題來了,怎麼樣量化地得出 JS 速度和原生語言的差距呢?
前端框架領域裡有很多 Todo MVC 的 benchmark,它們的對比方式可以簡單地理解為對比基於不同框架實現同樣功能的效能。這固然是可行且易於量化的,但不同的編碼實現和構建技巧可能顯著地改變跑分資料(如是否開啟 React 的優化外掛等),並且 Todo MVC 的場景也基本侷限於資料的增查改刪,還不是真實場景下的複雜應用。
注意到在今天,許多程式語言已經能夠編譯到 JavaScript,我們選擇了另一條方式:對比真實世界應用原生版和 JavaScript 版的效能。並且,在 Web Assembly 已經成為標準的背景下,我們還有機會對比原生程式碼、WASM 和 JS 的效能,這看起來就更有趣了。
怎麼樣選擇所測試的應用呢?我們需要計算密集型的場景來壓榨出執行時的效能,這方面的場景中最常見的就是遊戲和多媒體處理了。我們選擇了視訊編碼領域非常老牌的 FFmpeg,通過將它構建到 WASM 和 JavaScript 的方式,測試其不同版本編碼視訊的速度,以此得到原生、WASM 和 JS 的一個效能對比參考。
過程與結果
我們希望對比三種 FFmpeg 構建版使用預設配置編碼視訊時的速度:
- 原生
- JavaScript 版
- WASM 版
測試的輸入檔案是 JS 版 FFmpeg(即 videoconverter.js 庫)附帶的 Demo 視訊,測試平臺是 2015 款乞丐版 MacBook Pro。
原生
使用 Homebrew 安裝的版本:
time ffmpeg -i bigbuckbunny.webm output.mp4
複製程式碼
轉碼所需時長在 6s 左右。
JavaScript 版
videoconverter.js 附帶了一個開箱即用的靜態 Demo 頁,可以在其中轉碼視訊。選擇 Video to .MP4
輸出即可。轉碼所需時長在 140s 左右。
WASM 版
WASM 版 FFmpeg 沒有社群的穩定 release,這裡參考了這篇專欄的構建方式,在安裝 Emscripten 後從原始碼編譯 FFmpeg 到 WASM,將獲得的 ffmpeg.wasm 用於轉碼。
將一個 C 應用編譯到 WASM 後,我們可以選擇在 Web Worker 中使用它。只需將原本的命令列引數換一種格式傳遞即可:
ffmpeg({
arguments: [
'-i', '/input/demo.mp4',
'out.mp4'
],
files
}, function (results) {
self.postMessage(results[0].data)
})
複製程式碼
測得所需的時長在 24s 左右。
結論
我們觀察到了比較顯著的效能差異:
在這個計算密集型的場景下,JavaScript 的效能在原生的 1/20 左右,而 WASM 的效能可以達到原生的 1/4 左右(強調一遍,只是針對這個場景的結論,不是普適性的)。
但這個資料只能供參考之用,理由是整個鏈路中還有很多隱式的坑。列舉幾個:
- 視訊編碼測試的指標受演算法、引數影響很大。基於 FFmpeg 的預設配置未必能保證所執行的程式碼路徑一致。
- 將 FFmpeg 編譯到 WASM 時進行了很多剪裁,如禁用內聯的平臺相關彙編碼、禁用多執行緒等。這對最終構建出版本的效能是有影響的。
- WASM 很多時候並不能相比原生 JS 實現幾倍的效能提升。在一些沒有將整個應用編譯成 WASM,而是採用 JS 呼叫 WASM 模組的示例(如影象處理)中,頻繁地在 WASM 與 JS 之間複製資料的開銷很大,甚至會出現 WASM 效能不如 JS 的情況。
儘管有上面這些干擾因素存在,我們基本能確定的是,JavaScript 與原生之間的效能差距仍然可以是數量級的。只不過在目前常見的中後臺、活動頁、Hybrid 等場景下,我們很少需要用 JavaScript 處理計算密集型的任務,這時它不容易成為整個應用的瓶頸。
希望這個簡單的測試對於瞭解【JS 到底有多慢】能有一些幫助。不過我們的好訊息是,從另一個角度來看,移動端的 JS 已經不比 PC 端的 JS 跑得慢了,感興趣的同學不妨參考筆者的這篇文章。
最後列出文中涉及的相關資源: