WebAssembly 基礎(chǔ)
WebAssembly 介紹
官網(wǎng)介紹:?WebAssembly?或者?wasm?是一個(gè)可移植、體積小、加載快并且兼容 Web 的全新格式。分析一下?WebAssembly?這個(gè)詞由?Web?和?Assembly?組成,?Web?代表和前端有關(guān),?Assembly?匯編的意思,匯編對(duì)應(yīng)著機(jī)器碼,而機(jī)器碼對(duì)應(yīng)著指令集,那么什么是指令集呢?先來看一張圖片
圖片來自?WebAssembly入門-未來可能發(fā)生的巨變[1]?參考上圖,計(jì)算機(jī)的主要架構(gòu)如上。最底層是?CPU?的指令集,主要分為復(fù)雜指令集和簡(jiǎn)單指令集。復(fù)雜指令集是?x86?、?x64(也叫 x86-64, amd64)?兩種架構(gòu),專利在?Intel?和?AMD?兩家公司手里, 該架構(gòu)?CPU?主要是?Intel?和?AMD?兩家公司,這種?CPU?常用在?PC?機(jī)上,包括?Windows?,?macOS?和?Linux?。簡(jiǎn)單指令集是?arm?一種架構(gòu),專利在?ARM?公司手里,該架構(gòu)?CPU?主要有高通、三星、蘋果、華為海思、聯(lián)發(fā)科等公司。這種?CPU?常用在手機(jī)上,包括安卓和蘋果。那么什么是指令集呢?直接把阮一峰的老師的一個(gè)?例子[2]?粘過來,大家可以看一下。?c?語言的源程序。
int?add_a_and_b(int?a,?int?b)?{
?return?a?+?b;
}
int?main()?{
?return?add_a_and_b(2,?3);
}
所對(duì)應(yīng)的匯編就是下邊的樣子。
_add_a_and_b:
?push?%ebx
?mov?%eax,?[%esp+8]
?mov?%ebx,?[%esp+12]
?add?%eax,?%ebx
?pop?%ebx
?ret?
_main:
?push?3
?push?2
?call?_add_a_and_b
?add?%esp,?8
?ret
這里的?push?、?mov?每一條指令就是指令集規(guī)定的內(nèi)容,規(guī)定了操作碼、操作數(shù)以及具體的功能。當(dāng)然這里是用匯編表示的,主要是為了我們?nèi)祟悂碜x寫,最終還會(huì)轉(zhuǎn)成?0,1?序列。上邊每個(gè)單詞都會(huì)有一個(gè)數(shù)字相對(duì)應(yīng),比如?add?指令對(duì)應(yīng)?00000011?。通過規(guī)定的指令集(加法的指令,壓棧指令等),編寫相關(guān)程序,然后?CPU?就會(huì)一條一條的執(zhí)行,最終實(shí)現(xiàn)相應(yīng)的功能。而?WebAssembly?就規(guī)定了一套指令集,更準(zhǔn)確的來說是虛擬指令集,因?yàn)檫@套指令集是跑在虛擬機(jī)上的,而不是直接由硬件運(yùn)行。(指令內(nèi)容來源自?WebAssembly入門-未來可能發(fā)生的巨變[3]?)
簡(jiǎn)單來說就是,編譯器將?C++?,?Go?,?Rust?等編譯為中間代碼,再轉(zhuǎn)化為?WebAssembly?字節(jié)碼(類似java的字節(jié)碼),?WebAssembly?字節(jié)碼是一種抹平了不同?CPU?架構(gòu)的機(jī)器碼,?WebAssembly?字節(jié)碼不能直接在任何一種?CPU?架構(gòu)上運(yùn)行, 但由于非常接近機(jī)器碼,可以非??斓谋环g為對(duì)應(yīng)架構(gòu)的機(jī)器碼,因此?WebAssembly?運(yùn)行速度和機(jī)器碼接近。
WebAssembly.compile(new?Uint8Array(`
??00?61?73?6d??01?00?00?00??01?0c?02?60??02?7f?7f?01
??7f?60?01?7f??01?7f?03?03??02?00?01?07??10?02?03?61
??64?64?00?00??06?73?71?75??61?72?65?00??01?0a?13?02
??08?00?20?00??20?01?6a?0f??0b?08?00?20??00?20?00?6c
??0f?0b`.trim().split(/[\s\r\n]+/g).map(str?=>?parseInt(str,?16))
)).then(module?=>?{
??const?instance?=?new?WebAssembly.Instance(module)
??const?{?add,?square?}?=?instance.exports
console.log('2?+?4?=',?add(2,?4))
console.log('3^2?=',?square(3))
console.log('(2?+?5)^2?=',?square(add(2?+?5)))})
將上面代碼拷貝到瀏覽器控制臺(tái)就能看到結(jié)果。綜上所述可以知道wasm其實(shí)是一種二進(jìn)制字節(jié)碼,和機(jī)器碼比較類似,我們都知道匯編語言可以被轉(zhuǎn)化為機(jī)器碼,同樣wasm也有類似的匯編語言,被稱為?S-表達(dá)式?(?mdn定義[4]?),可以使用工具?wat2wasm?在?S-表達(dá)式?和?wasm?字節(jié)碼之間相互轉(zhuǎn)換
WebAssembly 由來
由于前端業(yè)務(wù)場(chǎng)景越來越多,業(yè)務(wù)越來越復(fù)雜,導(dǎo)致在一些性能比較差的機(jī)器上表現(xiàn)不好。表現(xiàn)不好一方面確實(shí)是業(yè)務(wù)場(chǎng)景比較復(fù)雜,另一方面就是?javascript?這門語言本身的缺陷。其一?javascript?是一門解釋型語言,首先解釋一下編譯型和解釋型編程語言的區(qū)別。編譯型:將源代碼經(jīng)過編譯器轉(zhuǎn)為機(jī)器碼,在執(zhí)行 解釋型:代碼運(yùn)行過程中,將源代碼經(jīng)過編譯器生成中間代碼,中間代碼再通過虛擬器生成目標(biāo)平臺(tái)的機(jī)器碼執(zhí)行。引用知乎上的一句話,編譯型就是提前做好一桌子菜,再吃,而解釋型就相當(dāng)于吃火鍋,一邊吃,一邊煮 所以說解釋型編譯語言會(huì)比編譯型語言慢些。個(gè)人認(rèn)為這個(gè)并不怎么影響性能。其二?javascript?是一門動(dòng)態(tài)語言編寫程序時(shí)無需考慮變量類型,對(duì)于開發(fā)人員確實(shí)友好,但是對(duì)于js引擎來說并不友好。在js引擎沒有引入JIT之前,假如一個(gè)方法被不同模塊調(diào)用10次,那么這個(gè)方法也會(huì)被解釋10次,顯然這是很浪費(fèi)性能。引入JIT后看一下v8引擎執(zhí)行js的過程
Js 源代碼經(jīng)過詞法分析、語法分析、語義分析生成AST樹,再生成中間代碼,中間代碼進(jìn)入解釋器中執(zhí)行,同時(shí)分析器會(huì)監(jiān)控代碼的執(zhí)行情況,如果一段代碼被反復(fù)執(zhí)行,那么這個(gè)段代碼被標(biāo)記為熱點(diǎn)代碼,同時(shí)會(huì)把這段代碼送到編譯器中編譯,同時(shí)生成優(yōu)化后機(jī)器碼,等下次執(zhí)行到這段代碼,就可以直接運(yùn)行優(yōu)化后的代碼。但是也有特殊的情況,因?yàn)閖s是動(dòng)態(tài)語言,類型是不固定的,有可能上一秒這個(gè)段代碼被標(biāo)記為熱點(diǎn)代碼,下一秒該熱點(diǎn)代碼對(duì)應(yīng)的優(yōu)化代碼可能就被丟棄。感興趣的可以看一下?這篇文章[5]
ASM.js
所以為了解決類型問題?WebAssembly?前身?ASM.js?出現(xiàn),?ASM.js?是?Mozilla?在?2013?年推出的,?ASM.js?是一個(gè)?javascript?嚴(yán)格子集,?ASM.js?雖然可以手寫,但是一般都是使用編譯器將?C++/C?或者一些其他語言轉(zhuǎn)為?ASM.js?文件。生成的?ASM.js?文件變量都是靜態(tài)類型,不用在運(yùn)行時(shí)判斷變量類型,從而使得?js?引擎可以采用?AOT(Ahead Of Time)?的編譯策略,也就是在運(yùn)行前直接編譯成機(jī)器碼,因此運(yùn)行速度會(huì)有一定的提升。那為什么?ASM.js?并沒有流行起來,其一它還是沒有一個(gè)統(tǒng)一的標(biāo)準(zhǔn)。它只是一個(gè)由一個(gè)廠商推出的,非標(biāo)準(zhǔn)的 JavaScript 子集而已,而它的使用者根據(jù)自己的偏好和習(xí)慣來使用它。WebAssembly 則不同,它是由幾大主要的瀏覽器廠商共同設(shè)計(jì)的。其二無論?ASM.js?如何優(yōu)化,歸根結(jié)底還是一個(gè)js文件,看一下上面v8引擎執(zhí)行js的圖片,可以看出只要是js文件,就需要經(jīng)過解析,生成中間代碼,而這兩步是JavaScript代碼在引擎執(zhí)行過程當(dāng)中消耗時(shí)間最多的兩步。而WebAssembly不用經(jīng)過這兩步。這就是WebAssembly比asm.js更快的原因。
Emscripten介紹
Emscripten 是一個(gè)針對(duì)WebAssembly的開源編譯器,它可以將C和C++代碼或任何其他使用LLVM的語言編譯成WebAssembly,并在Web、Node.js或其他wasm運(yùn)行時(shí)運(yùn)行它。Emscripten 中包含倆個(gè)重要的工具鏈
emcc 是clang和gcc的臨時(shí)替代品,emcc使用LLVM和clang將c/c++編譯為WebAssembly
EmscriptenSDK用于安裝整個(gè)工具鏈,包括EMCC和LLVM等。EmscriptenSDK(Emsdk)可以在Linux、Windows或MacOS上使用。
安裝
#?Get?the?emsdk?repo
git?clone?[https://github.com/emscripten-core/emsdk.git](https://github.com/emscripten-core/emsdk.git?"https://github.com/emscripten-core/emsdk.git")
#?Enter?that?directory
cd?emsdk
#?Fetch?the?latest?version?of?the?emsdk?(not?needed?the?first?time?you?clone)
git?pull
#?Download?and?install?the?latest?SDK?tools.
./emsdk?install?latest
#?Make?the?"latest"?SDK?"active"?for?the?current?user.?(writes?.emscripten?file)
./emsdk?activate?latest
#?Activate?PATH?and?other?environment?variables?in?the?current?terminal
source?./emsdk_env.sh
通過以上操作,在當(dāng)前命令行運(yùn)行?emcc -v?,打印如下代表安裝成功
emcc?-v
emcc?(Emscripten?gcc/clang-like?replacement?+?linker?emulating?GNU?ld)?2.0.29?(28ca7fb7ce895b21013212e4644a5794a15a76f9)
clang?version?14.0.0?([https://github.com/llvm/llvm-project](https://github.com/llvm/llvm-project?"https://github.com/llvm/llvm-project")?8e284be04f2cd43a821289133a759afa2844f935)
Target:?wasm32-unknown-emscripten
Thread?model:?posix
InstalledDir:?/Users/bytedance/Desktop/emsdk/upstream/bin
為了確保每次打開終端都可以使用emcc,還需要將如下代碼加入到?.bash_profile?或?.zsh
source?emcc目錄/emsdk_env.sh?&>?/dev/null
使用
將? c/c++?編譯為?js
//?test.cpp
#include?
using?namespace?std;
int?main()
{
????cout?<"Hello?World"?<endl;
}
運(yùn)行?emcc ./test.cpp?,同級(jí)目錄生成?a.out.js?和?a.out.wasm?文件,第二個(gè)是包含編譯代碼的WebAssembly文件,第一個(gè)是包含加載和執(zhí)行代碼的運(yùn)行時(shí)支持的JavaScript文件。然后可以使用?nodejs?運(yùn)行?js
node?./a.out.js
終端輸出?Hello World
Emscripten還可以生成用于測(cè)試嵌入式JavaScript的HTML。要生成HTML,請(qǐng)使用-o(輸出)命令并指定一個(gè)html文件作為目標(biāo)文件:
emcc?test.cpp?-o?hello.html
然后可以在瀏覽器中打開hello.html 不幸的是,一些瀏覽器(包括Chrome、Safari和Internet Explorer)不支持file://xhr請(qǐng)求,并且不能加載HTML所需的額外文件(如.wasm文件或下面提到的打包文件數(shù)據(jù))。對(duì)于這些瀏覽器,您需要使用本地Web服務(wù)器提供文件,然后打開?http://localhost:8000/hello.html[6]?). 本人在練習(xí)的時(shí)候使用的是?http-server?安裝
npm?i?http-server?-g
在工作目錄運(yùn)行
#?-o?打開瀏覽器
#?-p?指定端口
http-server?./?-o?-p?8085?
在瀏覽器中打開?8085?端口
WebAssembly 會(huì)取代 Javascript嗎?
我認(rèn)為不會(huì)。我認(rèn)為?WebAssembly?和?JavaScript?是相輔相成的,WebAssembly是被設(shè)計(jì)成JavaScript的一個(gè)完善、補(bǔ)充,而不是一個(gè)替代品。大家怎么認(rèn)為?
參考鏈接
Emscripten 官網(wǎng)[7]?c++項(xiàng)目轉(zhuǎn)成wasm全過程[8]?https://juejin.cn/post/6844903709806182413#heading-27[9]?https://juejin.cn/post/6844903469808091143[10]
WebAssembly入門-未來可能發(fā)生的巨變:?https://windliang.wang/2020/11/18/WebAssembly%E5%85%A5%E9%97%A8-%E6%9C%AA%E6%9D%A5%E5%8F%AF%E8%83%BD%E5%8F%91%E7%94%9F%E7%9A%84%E5%B7%A8%E5%8F%98/
[2]例子:?http://www.ruanyifeng.com/blog/2018/01/assembly-language-primer.html
[3]WebAssembly入門-未來可能發(fā)生的巨變:?https://windliang.wang/2020/11/18/WebAssembly%E5%85%A5%E9%97%A8-%E6%9C%AA%E6%9D%A5%E5%8F%AF%E8%83%BD%E5%8F%91%E7%94%9F%E7%9A%84%E5%B7%A8%E5%8F%98/
[4]mdn定義:?https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format
[5]這篇文章:?https://juejin.cn/post/6844903469262831624
[6]http://localhost:8000/hello.html:?http://localhost:8000/hello.html
[7]Emscripten 官網(wǎng):?https://emscripten.org/index.html
[8]c++項(xiàng)目轉(zhuǎn)成wasm全過程:?https://zhuanlan.zhihu.com/p/158586853
[9]https://juejin.cn/post/6844903709806182413#heading-27:?https://juejin.cn/post/6844903709806182413#heading-27
[10]https://juejin.cn/post/6844903469808091143:?https://juejin.cn/post/6844903469808091143

往期推薦



最后
歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...


