WebAssembly計算性能測試(JIT和非JIT版)
WebAssembly的一個優點是編譯生成的
WASM提供了一種統一的思路,傳說中可以把“一切”都編譯成WebAssembly二進製文件即wasm,實現“一次編譯,到處運行”。這種思路很具有誘惑力。
本文基於emcc測試WASM的性能。
當前emcc版本已經發展到了2.0.25,基本上bug不多,用起來也比較方便了。網上安裝教程眾多,在此不予贅述。
本文同樣以Cello中的benchmarks目錄下的Nbodies測試集為例。為了進一步加大計算量,我修改了裡面的例子,把迭代循環次數從100000加大到了10000000(放大了100倍)。具體參見:
同時WASM的執行需要nodejs或者是瀏覽器。那麼還有別的方便的東西可以執行嗎?當然有,比如WASM3。
WASM3是個什麼呢?
官網的說法是:Wasm3 是一個高性能的WebAssembly 解釋器。曾經還在Github 上看到,它比大多數其他類型的Wasm 解釋器在解釋執行Wasm 字節碼的速度上快8 倍左右;相對的,會比帶有JIT 的Wasm 編譯器慢4-5 倍。而比單純的Native Code 慢12 倍左右。從整體上來看作為一個解釋器,這樣的數據其實是十分不錯的。
當然以上這句話,現在在官網是看不到了。後面我有測評,wasm3沒有這麼好的性能,估計作者覺得不好意思,就把性能篇刪掉了。
那麼為什麼用wasm3呢?畢竟他用起來速度也不快啊! Wasm3 使用C 語言開發,可以成為項目中的一個庫而存在,這樣就可以在項目中輕鬆的執行wasm二進製文件了,用途廣得很。
首先,把nbodies_c.c和nbodies_cpp.cpp兩個文件生成wasm文件。
標準做法是:
emcc -O3 nbodies_c.c -o nbodies_c.js -s STANDALONE_WASM
運行則是:
node nbodies_c.js
怎麼可能會這麼簡單,必須有錯誤啊!
錯誤如下:

含有一條警告以及一個異常,反正就是不行。
不過wasm3倒是可以執行,wasm3的執行指令是:
wasm3 –repl nbodies_c.wasm
或者
wasm3 nbodies_c.wasm
都能執行。
不過以上執行時間需要9.312秒。
查看node生成的nbodies_c.wasm和nbodies_c.js可知,它生成了一堆亂七八糟的輔助js文件,這些輔助函數可以幫助於其運行與web端或者本地。 nbodies_c.js文件甚至高達123kb。
我顯然不需要這些亂七八糟的,那麼node可以重新來過,只生成wasm文件:
emcc -O2 nbodies_c.c -o nbodies_c4wasm3.wasm emcc -O2 nbodies_c.c -o nbodies_c4wasm3.wasm emcc -O2 nbodies_cpp.cpp -o nbodies_cpp4wasm3.wasm
這時wasm3對應的計算時間9.312秒。
wasm3對應的C++的時間為12.25秒。
現在修復node執行wasm的警告和異常問題
C版本
生成指令改為:
emcc nbodies_c.c -O2 -s WASM=1 -Wall -s MODULARIZE=1 -o nbodies_c.js -s WASM_BIGINT
當然,如下指令生成wasm文件也行:
emcc nbodies_c.c -O2 -o nbodies_c.js -s STANDALONE_WASM -s WASM_BIGINT
運行這條指令後的bug為:我裡面的不知道為什麼。所以還是別用這條指令了。
然後手寫一個簡單的js調用腳本runnbodies.js:
constModule=require('./nbodies_c.js');constwasm=Module({wasmBinaryFile:'nbodies_c.wasm'});wasm.onRuntimeInitialized=function(){console.log(wasm._main());};
顯然,以上就是簡單的執行C程序的main函數。
調用指令為:
node --experimental-wasm-bigint runnbodies.js
成功。 Nodejs執行C版的計算時間0.877sec。
C++版本
對應的c++版的生成指令為:
emcc nbodies_cpp.cpp -O2 -s WASM=1 -Wall -s MODULARIZE=1 -o nbodies_cpp.js -s WASM_BIGINT
然後手寫一個簡單的js調用腳本runnbodiescpp.js:
constModule=require('./nbodies_cpp.js');constwasm=Module({wasmBinaryFile:'nbodies_cpp.wasm'});wasm.onRuntimeInitialized=function(){console.log(wasm._main());};
調用指令為:
node --experimental-wasm-bigint runnbodiescpp.js
Nodejs執行C++版的計算時間0.998sec。
以上就把簡單的C或C++程序轉成了node或者wasm3可以執行的wasm二進製文件。

看了這個結果,發現node v8性能真是可以,加入jit,配合wasm,性能直逼原生C程序,這裡性能甚至超過了原版C++,不可思議,當然emcc的後台是clang,沒準它也有貢獻。以後再細細琢磨去。
Wasm沒有了jit的加持,性能馬上就變成了腳本語言。當然,這個計算能力在腳本語言中也是數一數二的霸主,參考這個測試結果:

具體細節見
可見,wasm3的計算能力遠超腳本性能王Lua,甚至還超過了純C平台的Cello,要知道官網提供的win平台下的wasm3可執行文件才247kb,這麼小的文件就能獲得這麼強悍的性能,還是值得在項目中採用的。畢竟在很多情況下,計算性能並不是首要考量,比如在MCU上等。
由以上測試發現,wasm3的普適性極好,nodejs的奇技淫巧它全不需要。用起來簡單粗暴。但是性能上就大打折扣了,缺了jit它立馬變成了一個腳本語言,當然現在wasm3成了腳本之王,Lua已經乾不過它了,除非上Luajit,可惜Luajit好像死翹翹了。