摘要:解析下載后,需要引擎經(jīng)過兩個(gè)階段轉(zhuǎn)換成,然后再轉(zhuǎn)換為瀏覽器需要的中間字節(jié)碼。未來所有支持編譯到字節(jié)碼的編程語言,理論上都可以輸出。
了解WebAssembly原理
WebAssembly是一種可以在瀏覽器上運(yùn)行的二進(jìn)制可執(zhí)行格式文件。它將成為瀏覽器進(jìn)化史上又一次革命。
自從瀏覽器問世以來,javascript就成為瀏覽器上執(zhí)行程序的唯一標(biāo)準(zhǔn),越來越多的應(yīng)用程序通過javascript開發(fā),并運(yùn)行于瀏覽器上;而隨著瀏覽器上h5程序功能的豐富,也對瀏覽器提出了更多的挑戰(zhàn)。其中一條最為重要的就是性能問題。javascript是一種弱類型,解釋性的腳本語言。它天生運(yùn)行速度慢,成為了很多h5應(yīng)用的軟肋。雖然2008年google V8引入了即時(shí)編譯等技術(shù)使js的運(yùn)行速度提升了一大截,但是一些大型應(yīng)用程序,比如游戲,視頻編輯,壓縮,算法等依然不適合運(yùn)行在瀏覽器上。
WebAssembly的到來解決了這個(gè)問題,并給開發(fā)基于瀏覽器的應(yīng)用程序提供了另外的編程語言選擇。2017年三大瀏覽器同時(shí)增加了WebAssembly支持,標(biāo)志著WebAssembly已經(jīng)達(dá)到生產(chǎn)實(shí)用標(biāo)準(zhǔn)。
為什么WebAssembly比javascript快回答這個(gè)問題需要洞悉瀏覽器執(zhí)行javascript代碼的各個(gè)環(huán)節(jié)。
瀏覽器加載并執(zhí)行javascript大概可分為如下幾個(gè)環(huán)節(jié): 下載,解析,執(zhí)行和優(yōu)化,垃圾回收。
javascript是以純文本格式下載的。相比,webassembly使用二進(jìn)制格式存儲(chǔ),結(jié)構(gòu)更精簡,更小。
解析javascript下載后,需要js引擎經(jīng)過tokenize, parse兩個(gè)階段轉(zhuǎn)換成AST(abstract syntax tree),然后再轉(zhuǎn)換為瀏覽器需要的中間字節(jié)碼。由于js是比較高級的語言,解析js也相對要做更多的事情。webassembly的格式類似于匯編語言,本來就是中間字節(jié)碼,和需要運(yùn)行的機(jī)器碼更相近,需要簡單的轉(zhuǎn)換工作即可轉(zhuǎn)化為CPU可以直接執(zhí)行的機(jī)器碼。
下圖是一個(gè)真實(shí)運(yùn)行的webassembly(它是文本的,只是為了方便調(diào)試),可以看出它和匯編是很相似的,更易轉(zhuǎn)化為機(jī)器碼。
執(zhí)行和優(yōu)化在執(zhí)行階段,js普遍采用解釋執(zhí)行策略,相當(dāng)于每一次執(zhí)行javascript指令都要通過js引擎中轉(zhuǎn)給cpu。現(xiàn)代的js引擎同時(shí)采用了即時(shí)編譯的策略。這需要同時(shí)運(yùn)行一個(gè)profiler,關(guān)注每個(gè)函數(shù)的調(diào)用情況。當(dāng)profiler發(fā)現(xiàn)一個(gè)函數(shù)調(diào)用的比較多的時(shí)候,會(huì)把這個(gè)函數(shù)拋給編譯器,為它生成一個(gè)更快的編譯版本。某些情況下,參數(shù)類型會(huì)發(fā)生變化。這時(shí),需要?jiǎng)h除之前的編譯版本,對新參數(shù)類型編譯新的版本。而webassembly由于類匯編的結(jié)構(gòu),只需簡單的編譯即可轉(zhuǎn)換為可直接運(yùn)行在cpu上的機(jī)器碼,執(zhí)行更快。
垃圾回收javascript運(yùn)行期間需要同時(shí)間歇的運(yùn)行一個(gè)垃圾回收器,掃描堆上的垃圾、釋放內(nèi)存。垃圾回收器的運(yùn)行又和js引擎的執(zhí)行是互斥的,導(dǎo)致js執(zhí)行間歇性的被垃圾回收器打斷。webassembly不負(fù)責(zé)垃圾回收,只能編程語言自行解決。于是不同的編程語言又有所不同。C/C++是手動(dòng)管理內(nèi)存(malloc/free, new/delete),rust則是基于生命周期的自動(dòng)內(nèi)存管理。所有這些內(nèi)存管理方法都不需要間歇的全局暫停。因此性能更好。
從以上各個(gè)角度看WebAssembly確實(shí)比javascript性能高。事實(shí)上,目前階段WebAssembly執(zhí)行時(shí)間大概等于原生程序執(zhí)行時(shí)間X1.2。
WebAssembly的加載與執(zhí)行wasm是WebAssembly格式的瀏覽器可執(zhí)行文件。它是二進(jìn)制的,但是它并不像桌面win32程序一樣,可以隨便使用系統(tǒng)資源,調(diào)用操作系統(tǒng)api。事實(shí)上,所有與外界相關(guān)的操作都必須由javascript傳入。比如:要申請一段內(nèi)存,必須由javascript申請了并傳給他。 瀏覽器上,javascript做不到的,它也做不到;javascript能做到的,它能做的更快。 這個(gè)就是它的價(jià)值。
目前必須要js啟動(dòng)WebAssembly的加載和實(shí)例化(后面可能會(huì)有多帶帶的加載機(jī)制)。
如下函數(shù),使用fetchAPI加載wasm文件,并實(shí)例化wasm模塊。
function fetchAndInstantiate(url, importObject) { return fetch(url).then(response => response.arrayBuffer() ).then(bytes => WebAssembly.instantiate(bytes, importObject) ).then(results => results.instance ); } fetchAndInstantiate("module.wasm", importObject).then(function(instance) { ... })
importObject即瀏覽器需要向webassembly注入的交互api。
如下,是一個(gè)真實(shí)運(yùn)行的importObject包括很多js函數(shù)。
注意global.memory就是webassembly程序執(zhí)行用到的內(nèi)存,是js申請的一個(gè)大的ArrayBuffer。
學(xué)會(huì)WebAssembly開發(fā)講了這么多WebAssembly的優(yōu)點(diǎn),接下就講下WebAssembly的開發(fā)。
開發(fā)WebAssembly并不意味著需要手寫WebAssembly匯編程序。一個(gè)開源項(xiàng)目emscripten已經(jīng)提供了sdk可以編譯C/C++,并輸出WebAssembly的wasm文件。目前,rust也已經(jīng)支持編譯到wasm。未來所有支持編譯到LLVM字節(jié)碼的編程語言,理論上都可以輸出wasm。
安裝emscripten下載emscripten sdk后,是個(gè)壓縮文件,其實(shí)是sdk包管理器。
需要執(zhí)行如下命令,完成sdk的安裝。
./emsdk update ./emsdk install latest ./emsdk activate latest source ./emsdk_env.sh
現(xiàn)在已經(jīng)有個(gè)可用的emcc編譯器了,輸入:
emcc --version
查看編譯器版本。
emsdk安裝后, emscripten文件內(nèi)是按版本號安裝的sdk內(nèi)容,里面有很多C/C++用例,可以自行研究下。
簡單demo這個(gè)簡單的C程序可以直接編譯為wasm。
#includeint main() { printf("hello, world! "); return 0; }
./emcc hello_world.c node a.out.js
默認(rèn)情況下,emcc只輸出了一個(gè)js(asmjs)。asmjs是webassembly的一個(gè)早期原型,可提供webassembly在舊版本瀏覽器上的兼容。按如下命令輸出webassembly二進(jìn)制wasm。
./emcc hello_world.c -s WASM=1 -o index.html
這次編譯輸出了index.html, index.js, index.wasm三個(gè)文件。通過一個(gè)靜態(tài)服務(wù)器打開index.html,可以看到console里的輸出。
這個(gè)index.html是一個(gè)調(diào)試頁面。生產(chǎn)上加載webassembly一般都需要自己寫index.html,只保留js和wasm文件就夠了。
以上的例子中,printf的標(biāo)準(zhǔn)輸出被定向到了瀏覽器的console里面。 系統(tǒng)API調(diào)用被換成了js實(shí)現(xiàn)。 事實(shí)上很多l(xiāng)ibc里面的函數(shù)被emscripten實(shí)現(xiàn)成了瀏覽器上的兼容方案,從而更好的和瀏覽器結(jié)合。
環(huán)境所有編程語言都要和它的運(yùn)行環(huán)境打交道,否則除了把cpu跑滿,沒什么實(shí)用價(jià)值。跑在瀏覽器上的webassembly則是通過和js相互調(diào)用發(fā)揮它的作用。
Emscripten sdk提供了很多API與js運(yùn)行環(huán)境/瀏覽器交互。定義在其中兩個(gè)頭文件中:
emscripten.h: 中定義了一些基礎(chǔ)功能相關(guān)API,包括調(diào)用js,文件讀寫,網(wǎng)絡(luò)請求等,這些API在node中也可以用。
html5.h中定義了瀏覽器中與DOM相關(guān)的各種操作,包括DOM,事件,設(shè)備相關(guān)等。
下面,抽出一些關(guān)鍵的API講下webassembly是如何與瀏覽器協(xié)同工作的。
調(diào)用jsEM_ASM宏,讓webassembly可以直接調(diào)用js。
EM_ASM(alert("hai"); alert("bai"));
如果需要從js獲取執(zhí)行結(jié)果,可以用EM_ASM_INT, EM_ASM_DOUBLE兩個(gè)版本分別獲取int和double類型的數(shù)值。
int x = EM_ASM_INT({ return $0 + 42; }, 100);
如果需要傳遞字符串給js,可以傳遞一個(gè)字符串起始的指針給js。由于js可以訪問整個(gè)wasm程序的內(nèi)存區(qū)域,js用這個(gè)指針就可以從內(nèi)存讀出字符串。Module對象上的UTF8ToString(ptr), UTF16ToString(ptr), UTF32ToString(ptr), Pointer_stringify(ptr, length)這幾個(gè)函數(shù)可獲得指針處的字符串。
char* sample = "This is a string"; EM_ASM_({ console.log("js got string:", Module.UTF8ToString($0)); }, sample);標(biāo)準(zhǔn)輸入輸出
標(biāo)準(zhǔn)輸出我們之前看過,printf最終被轉(zhuǎn)到Module.print,默認(rèn)是console.log實(shí)現(xiàn)。
標(biāo)準(zhǔn)錯(cuò)誤輸出最終會(huì)被轉(zhuǎn)到Module.printErr,默認(rèn)是console.error實(shí)現(xiàn)。
對標(biāo)準(zhǔn)輸入的讀取在瀏覽器上變成了一個(gè)prompt框。體驗(yàn)不好,盡量不要讀。
Emscripten支持兩種GUI展示方法。
DOM: wasm是可以調(diào)用js的,而js又可以操作DOM。因此,wasm可以通過js操作DOM,創(chuàng)建程序的GUI。
Webgl Canvas: 除了DOM,emscripten還可以提供了opengl es的瀏覽器實(shí)現(xiàn)。通過操作一個(gè)Webgl Canvas,把顯示內(nèi)容畫在Canvas上。
事件循環(huán)C++ GUI程序一般都有個(gè)事件循環(huán),其實(shí)就是個(gè)死循環(huán),反復(fù)獲取并處理GUI層面上的各種事件。這樣程序不會(huì)跑完main函數(shù)直接退出。webassembly程序跑在瀏覽器上,而瀏覽器本來就是事件驅(qū)動(dòng),已經(jīng)有了一個(gè)事件循環(huán)。假如不改動(dòng)直接上瀏覽器,就會(huì)卡死瀏覽器的GUI進(jìn)程。因此webassembly程序需要由瀏覽器控制事件循環(huán)。
emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop)函數(shù)接受一個(gè)函數(shù)的指針后,瀏覽器會(huì)根據(jù)fps按時(shí)調(diào)用傳入的函數(shù)。
#include存儲(chǔ)#include int frame = 0; void main_loop(void) { printf("frame: %d ", frame); frame++; } int main(void) { emscripten_set_main_loop(main_loop, 0, 1); return 0; }
瀏覽器隔離了程序直接操作存儲(chǔ)的權(quán)限,因而webapp是安全的,但很多C代碼都有同步操作文件的API,如open, write, close。為了兼容,emscripten實(shí)現(xiàn)了一個(gè)內(nèi)存文件系統(tǒng),可以通過全局對象FS訪問。
下圖,是FS對象下的函數(shù)。
另外,emcc還提供了--preload-file參數(shù),在webassembly程序加載的過程中,預(yù)加載文件放到虛擬文件系統(tǒng)中。
wasm中的文件雖然是內(nèi)存的,但是支持通過indexDB持久化。
如下js,mount一個(gè)indexdb的文件夾到/data目錄,然后FS.syncfs把indexdb中的文件同步到內(nèi)存。
FS.mkdir("/data"); FS.mount(IDBFS, {}, "/data"); FS.syncfs(true, function (err) { });
接下來,所有,/data目錄下的讀寫,都在內(nèi)存中的同步讀寫。當(dāng)程序關(guān)閉的時(shí)候,需要調(diào)用FS.syncfs(false, function(err){})把內(nèi)存中的文件反方向同步回indexdb。
庫emsdk提供了一些常用的C++庫的webassembly兼容版本。用emcc --show-ports命令顯示。如果要用SDL2,需要給emcc加入選項(xiàng)-s USE_SDL=2,鏈接SDL2庫。
目前,emcc內(nèi)置支持這些庫。
$ emcc --show-ports Available ports: zlib (USE_ZLIB=1; zlib license) libpng (USE_LIBPNG=1; zlib license) SDL2 (USE_SDL=2; zlib license) SDL2_image (USE_SDL_IMAGE=2; zlib license) ogg (USE_OGG=1; zlib license) vorbis (USE_VORBIS=1; zlib license) bullet (USE_BULLET=1; zlib license) freetype (USE_FREETYPE=1; freetype license) SDL2_ttf (USE_SDL_TTF=2; zlib license) SDL2_net (zlib license) Binaryen (Apache 2.0 license) cocos2d
如果所需要的庫沒在列表里,需要先用emsdk編譯所需要的庫(可能涉及到庫的改動(dòng))。再編譯并鏈接,輸出最終目標(biāo)。emcc不支持動(dòng)態(tài)鏈接。
展望目前,webassembly已經(jīng)完成MVP最小功能版本開發(fā),有非常注目的性能。可以遇見,未來將有更多h5 app/游戲通過webassembly獲得更好的體驗(yàn)。使用C/C++/rust進(jìn)行webapp開發(fā),混合編程,也會(huì)有很多不錯(cuò)的探索。
未來h5能否通過webassembly撼動(dòng)原生的大門,讓我們拭目以待。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/92400.html
摘要:在當(dāng)前階段,僅僅只是字節(jié)碼規(guī)范。如果都沒有將代碼編譯為字節(jié)碼的工具,要起步就很困難了。接下來要做的是使用將格式的代碼轉(zhuǎn)換為二進(jìn)制碼。運(yùn)行文件,最后就能得到瀏覽器需要的真正的二進(jìn)制碼。 本文轉(zhuǎn)載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/1031原文:http://cultureofdevelopment.com/blog/build-your-fi...
摘要:年前端有哪些領(lǐng)域,技術(shù)值得關(guān)注,哪些技術(shù)會(huì)興起,哪些技術(shù)會(huì)沒落。自從谷歌提出后,就持續(xù)的獲得了業(yè)界的關(guān)注,熱度可見一斑。就在今年,谷歌也宣布將獲得與安卓原生應(yīng)用同等的待遇與權(quán)限。但是無論都值得關(guān)注。 1.前言 2017悄然過去,2018已經(jīng)來到。人在進(jìn)步,技術(shù)在發(fā)展。2018年前端有哪些領(lǐng)域,技術(shù)值得關(guān)注,哪些技術(shù)會(huì)興起,哪些技術(shù)會(huì)沒落。下面就我個(gè)人的判斷進(jìn)行一個(gè)預(yù)測判斷,希望能對大家...
摘要:年前端有哪些領(lǐng)域,技術(shù)值得關(guān)注,哪些技術(shù)會(huì)興起,哪些技術(shù)會(huì)沒落。自從谷歌提出后,就持續(xù)的獲得了業(yè)界的關(guān)注,熱度可見一斑。就在今年,谷歌也宣布將獲得與安卓原生應(yīng)用同等的待遇與權(quán)限。但是無論都值得關(guān)注。 1.前言 2017悄然過去,2018已經(jīng)來到。人在進(jìn)步,技術(shù)在發(fā)展。2018年前端有哪些領(lǐng)域,技術(shù)值得關(guān)注,哪些技術(shù)會(huì)興起,哪些技術(shù)會(huì)沒落。下面就我個(gè)人的判斷進(jìn)行一個(gè)預(yù)測判斷,希望能對大家...
摘要:目前正在開發(fā)兩個(gè)編譯器系統(tǒng)。這就意味著有很多功能還在襁褓之中,沒有經(jīng)過徹底思考以及實(shí)際驗(yàn)證。這些特性叫做未來特性。實(shí)現(xiàn)這一功能將會(huì)使用中的,而這一功能的實(shí)現(xiàn)將會(huì)提高程序執(zhí)行的效率。目前瀏覽器在逐漸支持用標(biāo)記來加載模塊。 作者:Lin Clark 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58ce7fd3a6...
閱讀 1459·2021-09-30 09:57
閱讀 1466·2021-09-09 09:33
閱讀 2220·2021-09-04 16:40
閱讀 1792·2021-09-01 10:50
閱讀 3237·2021-09-01 10:31
閱讀 2539·2019-08-30 15:56
閱讀 2970·2019-08-30 15:44
閱讀 3475·2019-08-29 17:29