為什么WebAssembly不是JavaScript的終結(jié)者,而是它的“助推器”?

導(dǎo)語 | 自從JavaScript創(chuàng)建到現(xiàn)在,每10年都會有新的變化,下一個10年的爆點在哪,可能就是WebAssembly!但WebAssembly絕不是JavaScript的終結(jié)者,反而是它的“助推器”!這是為什么呢?接下來我將帶你揭曉答案,讓你10分鐘快速掌握WebAssembly!
一、了解WebAssembly
(一)什么是WebAssembly?
官網(wǎng)定義:WebAssembly/wasm WebAssembly或者wasm是一個可移植、體積小、加載快并且兼容Web的全新格式(二進制),是由主流瀏覽器廠商組成的W3C社區(qū)團體制定的一個新的規(guī)范。
(二)發(fā)展歷史

1995年,大神布蘭登·艾奇(Brendan Eich),僅僅花了10天就將偉大的JavaScript擼出來了,引起了轟動。但是Js的設(shè)計初衷是想設(shè)計出一個面向非專業(yè)編程人員和網(wǎng)頁設(shè)計師的解釋型語言。由于時間太短,細節(jié)考慮的不夠周全,導(dǎo)致留下很多坑,所以后來很長一段時間,JavaScript的執(zhí)行速度一直備受詬病。
2008年,瀏覽器的性能大戰(zhàn)打響,眾多瀏覽器引入了即時(JIT)編譯使得JavaScript運行速度快了一個量級。但是對于JavaScript這種弱數(shù)據(jù)類型的語言來說,要實現(xiàn)一個完美的JIT非常難。因為Javascript是一個沒有類型的語言,而且像+這樣的符號又能夠重載,譬如這樣的代碼:
const sum = (a, b, c) => a + b + c;這是一個求和函數(shù),可以直接放在瀏覽器的控制臺下運行,如果傳參都是整數(shù)時,結(jié)果是整數(shù)相加的結(jié)果:如console.log(sum(1, 2, 3)),答案是6。但是,如果至少有一個是字符串,則結(jié)果是按照字符串拼接出的結(jié)果,如console.log(sum('1', 2, 3)),答案是“123”。也就是說,JIT在遇到第一個sum時會編譯成整數(shù)相加的機器碼;但是在碰到第二個sum調(diào)用時,不得不重新編譯一遍。這樣一來,JIT帶來的效率提升便被抵消了。
隨著JS達到了性能天花板,在當前復(fù)雜運算及游戲面前已完全力不從心。無法滿足一些大型web項目開發(fā),于是三大瀏覽器巨頭分別提出了自己的解決方案:

我們熟知的四大主流瀏覽器廠商Google Chrome、Apple Safari、Microsoft Edge和Mozilla FireFox,覺得Mozilla FireFox所推出的asm.js很有前景,為了讓大家都能使用,于是他們就共同參與開發(fā),基于asm.js制定一個標準,也就是WebAssembly。

2015年,WebAssembly首次發(fā)布,并可直接在瀏覽器中運行。
2017年3月份,四大廠商均宣布已經(jīng)于最新版本的瀏覽器中支持了WebAssembly的初始版本,這意味著WebAssembly技術(shù)已經(jīng)實際落地。
2019年,被正式加入Web的標準大家庭中。
(三)WebAssembly影響
大幅度提高Javascript的性能,同時也不損失安全性。Webapp和原生App的性能差距變得很小。
基本之前需要插件來提高速度的技術(shù)已經(jīng)沒有必要了,網(wǎng)頁應(yīng)用的移植性會變得更好。
WebAssembly可以允許任何語言編譯到它制定的AST tree,相當于使用其他高級語言寫的代碼可以直接在網(wǎng)頁上運行。
(四)工作原理

WebAssembly的工作原理簡要來說是將C,C++, Rust等靜態(tài)語言通過編譯器的程序編譯成瀏覽器能夠運行的wasm二進制文件,當瀏覽器加載wasm文件后編譯為本地機器碼后運行。
為什么能提升當前js的性能?
正常的JS:在瀏覽器中,對JavaScript源碼進行解析,生成抽象語法樹或者字節(jié)碼(parse),JIT編譯器會對生成的代碼進行編譯優(yōu)化,當然后當發(fā)生去優(yōu)化時,再去重新編譯優(yōu)化,最后執(zhí)行。
WebAssembly:則省去了比較耗時的解析和編譯的過程,是直接生成的二進制可執(zhí)行機器碼進行執(zhí)行。
(五)瀏覽器支持

由圖可見,無論是PC、移動端還是服務(wù)器,都已經(jīng)開始支持WebAssembly了,這也說明WebAssembly已經(jīng)開始普及。
二、實戰(zhàn)演練
(一)語言選擇
實戰(zhàn)開始:首先確認你選擇開發(fā)的語言:
當你在用C/C++之類的語言編寫模塊時,你可以使用Emscripten來將它編譯到WebAssembly。
當你使用Rust語言編寫模塊時,需要一個額外工具wasm-pack。它會把代碼編譯成WebAssembly并制造出正確的npm包。
當你使用Java語言來編寫模塊時,據(jù)說TeaVM可以將JVM字節(jié)碼翻譯成JavaScript,還能翻譯成WebAssembly,現(xiàn)在還不成熟。
當你使用php語言來編寫模塊時,php2wasm可以接把PHP代碼編譯成wasm,現(xiàn)在還不成熟。
如果你還想保持js的編寫風格,那就用typescript來編寫吧,用assemblyscript來生成wasm。
確認好你要選擇的語言語種,應(yīng)該總有一款適合你的!
如果還不夠,請移位:
https://github.com/appcypher/awesome-wasm-langs
(二)環(huán)境準備
根據(jù)官網(wǎng)的引導(dǎo),使用C/C++來編寫部分代碼,并在瀏覽器中運行,以下均是在MacOS環(huán)境下進行的操作。
首先搭建Emscripten
沒有升級過python環(huán)境的同學,電腦會有個默認的版本python2.7.x,此時需要到phthon官網(wǎng)下載最新的python版本進行安裝。
在應(yīng)用程序中,雙擊“install Certificates.command”,否則會出現(xiàn)證書驗證異常,導(dǎo)致無法后續(xù)步驟。

#通過一個 git 克隆獲取 emscriptengit clone https://github.com/juj/emsdk.git#下載,安裝并激活 sdk,這個步驟可能需要一點時間cd emsdkinstall latestactivate latest#讓環(huán)境生效source ./emsdk_env.sh#確認安裝的內(nèi)容可以正常運行emcc --version

OK,可以進行代碼編寫了!
(三)樣例編寫
用C語言編寫的斐波那契數(shù)列(遞歸)
int fib(int n) {return n <= 1? 1: fib(n - 1) + fib(n - 2);}
編譯生成wasm
emcc fib.c -O3 -s WASM=1 -s SIDE_MODULE=1 -s EXPORTED_FUNCTIONS='["_fib"]' -o fib.wasm(注:emcc就是Emscripten編譯器指令,fib.c是輸入文件,-s SIDE_MODULE=1表示這就是一個模塊,-s EXPORTED_FUNCTIONS表示導(dǎo)出的接口函數(shù),-o fib.wasm`是輸出的文件,更多的命令字可參考官網(wǎng))
通過以上命令可生成名字為fib的wasm文件,可在js中進行引用,并且調(diào)用。
如何加載wasm
直接引用到頁面中,官網(wǎng)是推薦兩種,一個是fetch,一個是XMLHttpRequest,本文以fetch為例,在html頁面中引入上面的文件,如下:
fetch('你引入wasm路徑').then(res => {return res.arrayBuffer()}) //引入到內(nèi)存中,使其在array buffer中可用.then(WebAssembly.instantiate) //編譯和實例化 WebAssembly 代碼.then(module => {//寫你引用此模塊的目的})
將fib.c生成的fib.wasm后,在html中引用如下:
fetch('./wasm/fib.wasm').then(res => {return res.arrayBuffer()}).then(WebAssembly.instantiate).then(module => {// console.log(module.instance.exports.fib(value))let res = module.instance.exports.fib(value);$("#result").text(res);let endtime = new Date().getTime();$("#period").text(endtime - starttime);})
以上,環(huán)境和相關(guān)demo已經(jīng)寫好了,下面來看一下WebAssembly的執(zhí)行性能。
(四)性能比較
在demo頁面中同樣用js寫了一個遞歸的方法,和同時引用fib.wasm,做了以下性能比較:

為了減少誤差性,在代碼中分別用js和wasm做定時請求N次,來看他們的耗時,如下圖所示:

可以看到,同樣是計算40的遞歸算法,js時間基本上都是在1270ms左右,而編譯生成的wasm基本上都在680ms左右,也就是說在處理40的遞歸下,性能提升至原來的1.87倍。
同時,為了進行性能上的對比,對遞歸數(shù)做了不同的取值,來看請求結(jié)果及耗時,如下圖所示:

可以看到,遞歸數(shù)越大,也就是運算層次越多,webassembly相比于JS的優(yōu)勢就越明顯,也就是在比較復(fù)雜的JS運算或者處理中,用webassembly會更合適。
三、如何與JS互通
交互離不開相互調(diào)用,在瀏覽器中,了解到了在js中如何調(diào)用WebAssembly中的接口,那在WebAssembly中如何引用js相關(guān)函數(shù)呢?下面簡單和您介紹下。
(一)方法調(diào)用
Emscripten提供兩種方法讓C/C++調(diào)用JavaScript:
使用emscripten_run_script()運行js腳本,一種是寫“內(nèi)聯(lián)JavaScript”。
emscripten_run_script("alert('hi')");用EM_ASM()和其他相關(guān)宏寫內(nèi)聯(lián)JavaScript,稍快,這個是推薦的寫法。
int main() {EM_ASM(alert('hello world!');throw 'all done';);return 0;}
(二)示例demo
命令行,生成可執(zhí)行的html文件:
emcc test.c -s WASM=1 -o test.html運行結(jié)果:

可以看到,無論是哪種引用方式,都可以運行出你想要的結(jié)果。
四、總結(jié)
一句話:體積小,速度快,二進制文件,執(zhí)行效率高。
適用場景:在瀏覽器中使用視頻、游戲、AR、AI等比較合適使用WebAssembly,如果將服務(wù)器上的加密,想要放在web端用這個實現(xiàn)也可以。
市場現(xiàn)狀:flv.js用WebAssembly重寫后性能有很大提升;AutoCAD,Google Earth,用WebAssembly都開始支持了web版本等等。
突破:很多靜態(tài)語言轉(zhuǎn)成wasm的生態(tài)工具還不完善不成熟,都還處于起步階段;另外學習資料太少,還需要更多的人去探索去踩坑。
另希望有對WebAssembly感興趣的同學,可以一起交流探討。
參考鏈接:
1.https://developer.ibm.com/zh/technologies/web-development/articles/wa-lo-webassembly-status-and-reality/
2.https://zhuanlan.zhihu.com/p/49464798
3.https://blog.csdn.net/liubiggun/article/details/79848948
作者簡介
任春燕(helenren)
騰訊高級運營開發(fā)工程師
騰訊高級運營開發(fā)工程師,目前從事前端開發(fā)相關(guān)工作,有豐富的Web前端,大前端,小游戲等開發(fā)經(jīng)驗。
推薦閱讀
事務(wù)消息大揭秘!RocketMQ、Kafka、Pulsar全方位對比
Linux入門必看:如何在60秒內(nèi)分析Linux性能?
“Docker VS Kubernetes”是共生還是相愛相殺?


