現(xiàn)在,我們將會(huì)剖析 WebAssembly 的工作原理,而最重要的是它和 JavaScript 在性能方面的比對(duì):加載時(shí)間,執(zhí)行速度,垃圾回收,內(nèi)存使用,平臺(tái) API 訪問(wèn),調(diào)試,多線程以及可移植性。 我們構(gòu)建網(wǎng)頁(yè)程序的方式正面臨著改革-這只是個(gè)開(kāi)始而我們對(duì)于網(wǎng)絡(luò)應(yīng)用的思考方式正在發(fā)生改變。 首先,認(rèn)識(shí)下 WebAssembly 吧WebAssembly(又稱 wasm) 是一種用于開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用的高效,底層的字節(jié)碼。 WASM 讓你在其中使用除 JavaScript 的語(yǔ)言以外的語(yǔ)言(比如 C, C++, Rust 及其它)來(lái)編寫應(yīng)用程序,然后編譯成(提早) WebAssembly。 構(gòu)建出來(lái)的網(wǎng)絡(luò)應(yīng)用加載和運(yùn)行速度都會(huì)非???。 加載時(shí)間為了加載 JavaScript,瀏覽器必須加載所有文本格式的 js 文件。 瀏覽器會(huì)更加快速地加載 WebAssembly,因?yàn)?WebAssembly 只會(huì)傳輸已經(jīng)編譯好的 wasm 文件。而且 wasm 是底層的類匯編語(yǔ)言,具有非常緊湊的二進(jìn)制格式。 執(zhí)行速度如今 Wasm 運(yùn)行速度只比原生代碼慢 20%。無(wú)論如何,這是一個(gè)令人驚喜的結(jié)果。它是這樣的一種格式,會(huì)被編譯進(jìn)沙箱環(huán)境中且在大量的約束條件下運(yùn)行以保證沒(méi)有任何安全漏洞或者使之強(qiáng)化。和真正的原生代碼比較,執(zhí)行速度的下降微乎其微。另外,未來(lái)將會(huì)更加快速。 更讓人高興的是,它具備很好的瀏覽器兼容特性-所有主流瀏覽器引擎都支持 WebAssembly 且運(yùn)行速度相關(guān)無(wú)幾。 為了理解和 JavaScript 對(duì)比,WebAssembly 的執(zhí)行速度有多快,你應(yīng)該首先閱讀之前的 JavaScript 引擎工作原理的文章。 讓我們快速瀏覽下 V8 的運(yùn)行機(jī)制: 左邊是 JavaScript 源碼,包含 JavaScript 函數(shù)。首先,源碼先把字符串轉(zhuǎn)換為記號(hào)以便于解析,之后生成一個(gè)語(yǔ)法抽象樹(shù)。 語(yǔ)法抽象樹(shù)是你的 JavaScript 程序邏輯的內(nèi)存中圖示。一旦生成圖示,V8 直接進(jìn)入到機(jī)器碼階段。你基本上是遍歷樹(shù),生成機(jī)器碼然后獲得編譯后的函數(shù)。這里沒(méi)有任何真正的嘗試來(lái)加速這一過(guò)程。 現(xiàn)在,讓我們看一下下一階段 V8 管道的工作內(nèi)容: 現(xiàn)在,我們擁有 TurboFan ,它是 V8 的優(yōu)化編譯程序之一。當(dāng) JavaScript 運(yùn)行的時(shí)候,大量的代碼是在 V8 內(nèi)部運(yùn)行的。TurboFan 監(jiān)視運(yùn)行得慢的代碼,引起性能瓶頸的地方及熱點(diǎn)(內(nèi)存使用過(guò)高的地方)以便優(yōu)化它們。它把以上監(jiān)視得到的代碼推向后端即優(yōu)化過(guò)的即時(shí)編譯器,該編譯器把消耗大量 CPU 資源的函數(shù)轉(zhuǎn)換為性能更優(yōu)的代碼。 它解決了性能的問(wèn)題,但是缺點(diǎn)即是分析代碼及辨別哪些代碼需要優(yōu)化的過(guò)程也是會(huì)消耗 CPU 資源的。這也即意味著更多的耗電量,特別是在手機(jī)設(shè)備。 但是,wasm 并不需要以上的全部步驟-它如下所示插入到執(zhí)行過(guò)程中: wasm 在編譯階段就已經(jīng)通過(guò)了代碼優(yōu)化??傊?,解析也不需要了。你擁有優(yōu)化后的二進(jìn)制代碼可以直接插入到后端(即時(shí)編譯器)并生成機(jī)器碼。編譯器在前端已經(jīng)完成了所有的代碼優(yōu)化工作。 由于跳過(guò)了編譯過(guò)程中的不少步驟,這使得 wasm 的執(zhí)行更加高效。 內(nèi)存模型舉個(gè)栗子,一個(gè) C++ 的程序的內(nèi)存被編譯為 WebAssembly,它是整段連續(xù)的沒(méi)有空洞的內(nèi)存塊。wasam 中有一個(gè)可以用來(lái)提升代碼安全性的功能即執(zhí)行堆棧和線性內(nèi)存隔離的概念。在 C++ 程序中,你有一塊動(dòng)態(tài)內(nèi)存區(qū),你從其底部分配獲得內(nèi)存堆棧,然后從其頂部獲得內(nèi)存來(lái)增加內(nèi)存堆棧的大小。你可以獲得一個(gè)指針然后在堆棧內(nèi)存中遍歷以操作你不應(yīng)該接觸到的變量。 這是大多數(shù)可疑軟件可以利用的漏洞。 WebAssembly 采用了完全不同的內(nèi)存模型。執(zhí)行堆棧和 WebAssembly 程序本身是隔離開(kāi)來(lái)的,所以你無(wú)法從里面進(jìn)行修改和改變諸如變量值的情形。同樣地,函數(shù)使用整數(shù)偏移而不是指針。函數(shù)指向一個(gè)間接函數(shù)表。之后,這些直接的計(jì)算出的數(shù)字進(jìn)入模塊中的函數(shù)。它就是這樣運(yùn)行的,這樣你就可以同時(shí)引入多個(gè) wasm 模塊,偏移所有索引且每個(gè)模塊都運(yùn)行良好。 更多關(guān)于 JavaScript 內(nèi)存模型和管理的文章詳見(jiàn)這里。 內(nèi)存垃圾回收你已經(jīng)知曉 JavaScript 的內(nèi)存管理是由內(nèi)存垃圾回收器處理的。 WebAssembly 的情況有點(diǎn)不太一樣。它支持手動(dòng)操作內(nèi)存的語(yǔ)言。你也可以在你的 wasm 模塊中內(nèi)置內(nèi)存垃圾回收器,但這是一項(xiàng)復(fù)雜的任務(wù)。 目前,WebAssembly 是專門圍繞 C++ 和 RUST 的使用場(chǎng)景設(shè)計(jì)的。由于 wasm 是非常底層的語(yǔ)言,這意味著只比匯編語(yǔ)言高一級(jí)的編程語(yǔ)言會(huì)容易被編譯成 WebAssembly。C 語(yǔ)言可以使用 malloc,C++ 可以使用智能指針,Rust 使用完全不同的模式(一個(gè)完全不同的話題)。這些語(yǔ)言沒(méi)有使用內(nèi)存垃圾回收器,所以他們不需要所有復(fù)雜運(yùn)行時(shí)的東西來(lái)追蹤內(nèi)存。WebAssembly 自然就很適合于這些語(yǔ)言。 另外,這些語(yǔ)言并不能夠 100% 地應(yīng)用于復(fù)雜的 JavaScript 使用場(chǎng)景比如監(jiān)聽(tīng) DOM 變化 。用 C++ 來(lái)寫整個(gè)的 HTML 程序是毫無(wú)意義的因?yàn)?C++ 并不是為此而設(shè)計(jì)的。大多數(shù)情況下,工程師用使用 C++ 或 Rust 來(lái)編寫 WebGL 或者高度優(yōu)化的庫(kù)(比如大量的數(shù)學(xué)運(yùn)算)。 然而,將來(lái) WebAssembly 將會(huì)支持不帶內(nèi)存垃圾回功能的的語(yǔ)言。 平臺(tái)接口訪問(wèn)依賴于執(zhí)行 JavaScript 的運(yùn)行時(shí)環(huán)境,可以通過(guò) JavaScript 程序來(lái)直接訪問(wèn)這些平臺(tái)所暴露出的指定接口。比如,當(dāng)你在瀏覽器中運(yùn)行 JavaScript,網(wǎng)絡(luò)應(yīng)用可以調(diào)用一系列的網(wǎng)頁(yè)接口來(lái)控制瀏覽器/設(shè)備的功能且訪問(wèn) DOM,CSSOM,WebGL,IndexedDB,Web Audio API 等等。 然而,WebAssembly 模塊不能夠訪問(wèn)任何平臺(tái)的接口。所有的這一切都得由 JavaScript 來(lái)進(jìn)行協(xié)調(diào)。如果你想在 WebAssembly 模塊內(nèi)訪問(wèn)一些指定平臺(tái)的接口,你必須得通過(guò) JavaScript 來(lái)進(jìn)行調(diào)用。 舉個(gè)栗子,如果你想要使用 console.log,你就得通過(guò)JavaScript 而不是 C++ 代碼來(lái)進(jìn)行調(diào)用。而這些 JavaScript 調(diào)用會(huì)產(chǎn)生一定的性能損失。 情況不會(huì)一成不變的。規(guī)范將會(huì)為在未來(lái)為 wasm 提供訪問(wèn)指定平臺(tái)的接口,這樣你就可以不用在你的程序中內(nèi)置 JavaScript。 源碼映射當(dāng)你壓縮了 JavaScript 代碼的時(shí)候,你需要有合適的方法來(lái)進(jìn)行調(diào)試。 這時(shí)候源碼映射就派上用場(chǎng)了。 大體上,源碼映射就是把合并/壓縮了的文件映射到未構(gòu)建狀態(tài)的一種方式。當(dāng)你為生產(chǎn)環(huán)境進(jìn)行代碼構(gòu)建的時(shí)候,與壓縮和合并 JavaScript 一起,你會(huì)生成源碼映射用來(lái)保存原始文件信息。當(dāng)你想在生成的 JavaScript 代碼中查詢特定的行和列的代碼的時(shí)候,你可以在源碼映射中進(jìn)行查找以返回代碼的原始位置。 由于沒(méi)有規(guī)范定義源碼映射,所以目前 WebAssembly 并不支持,但最終會(huì)有的(可能快了)。 當(dāng)你在 C++ 代碼中設(shè)置了斷點(diǎn),你將會(huì)看到 C++ 代碼而不是 WebAssembly。至少,這是 WebAssembly 源碼映射的目標(biāo)吧。 多線程JavaScript 是單線程的。有很多方法來(lái)利用事件循環(huán)和使用在之前的文章中有提到的異步編程。 JavaScript 也使用 Web Workers 但是只有在極其特殊的情況下-大體上,可以把任何可能阻塞 UI 主線程的密集的 CPU 計(jì)算移交給 Web Worker 執(zhí)行以獲得更好的性能。但是,Web Worker 不能夠訪問(wèn) DOM。 目前 WebAssembly 不支持多線程。但是,這有可能是接下來(lái) WebAssembly 要實(shí)現(xiàn)的。Wasm 將會(huì)接近實(shí)現(xiàn)原生的線程(比如,C++ 風(fēng)格的線程)。擁有真正的線程將會(huì)在瀏覽器中創(chuàng)造出很多新的機(jī)遇。并且當(dāng)然,會(huì)增加濫用的可能性。 可移植性現(xiàn)在 JavaScript 幾乎可以運(yùn)行于任意的地方,從瀏覽器到服務(wù)端甚至在嵌入式系統(tǒng)中。 WebAssembly 設(shè)計(jì)旨在安全性和可移植性。正如 JavaScript 那樣。它將會(huì)在任何支持 wasm 的環(huán)境(比如每個(gè)瀏覽器)中運(yùn)行。 WebAssembly 擁有和早年 Java 使用 Applets 來(lái)實(shí)現(xiàn)可移植性的同樣的目標(biāo)。 WebAssembly 使用場(chǎng)景WebAssembly 的最初版本主要是為了解決大量計(jì)算密集型的計(jì)算的(比如處理數(shù)學(xué)問(wèn)題)。最為主流的使用場(chǎng)景即游戲-處理大量的像素。 你可以使用你熟悉的 OpenGL 綁定來(lái)編寫 C++/Rust 程序,然后編譯成 wasm。之后,它就可以在瀏覽器中運(yùn)行。 瀏覽下(在火孤中運(yùn)行)-h(huán)ttp://s3./mozilla-games/tmp/2017-02-21-SunTemple/SunTemple.html。這是運(yùn)行于Unreal engine(這是一個(gè)可以用來(lái)開(kāi)發(fā)虛擬現(xiàn)實(shí)的開(kāi)發(fā)套件)中的。 另一個(gè)合理使用 WebAssembly (高性能)的情況即實(shí)現(xiàn)一些處理計(jì)算密集型的庫(kù)。比如,一些圖形操作。 正如之前所提到的,wasm 可以有效減少移動(dòng)設(shè)備的電力損耗(依賴于引擎),這是由于大多數(shù)的步驟已經(jīng)在編譯階段提前處理完成。 未來(lái),你可以直接使用 WASM 二進(jìn)制庫(kù)即使你沒(méi)有編寫編譯成它的代碼。你可以在 NPM 上面找到一些開(kāi)始使用這項(xiàng)技術(shù)的項(xiàng)目。 針對(duì)操作 DOM 和頻繁使用平臺(tái)接口的情況 ,使用 JavaScript 會(huì)更加合理,因?yàn)樗粫?huì)產(chǎn)生額外的性能開(kāi)銷且它原生支持各種接口。 在 SessionStack 我們一直致力于持續(xù)提升 JavaScript 的性能以編寫高質(zhì)量和高效的代碼。我們的解決方案必須擁有閃電般的性能因?yàn)槲覀儾荒軌蛴绊懹脩舫绦虻男阅堋R坏┠惆?SessionStack 整合進(jìn)你的網(wǎng)絡(luò)應(yīng)用或網(wǎng)站的生產(chǎn)環(huán)境,它會(huì)開(kāi)始記錄所有的一切:所有的 DOM 變化,用戶交互,JavaScript 異常,堆棧追蹤,失敗的網(wǎng)絡(luò)請(qǐng)求和調(diào)試數(shù)據(jù)。所有的這一切都是在你的生產(chǎn)環(huán)境中產(chǎn)生且沒(méi)有影響到你的產(chǎn)品的任何交互和性能。我們必須極大地優(yōu)化我們的代碼并且盡可能地讓它異步執(zhí)行。 我們不僅僅有庫(kù),還有其它功能!當(dāng)你在 SessionStack 中重放用戶會(huì)話,我們必須渲染問(wèn)題產(chǎn)生時(shí)你的用戶的瀏覽器所發(fā)生的一切,而且我們必須重構(gòu)整個(gè)狀態(tài),允許你在會(huì)話時(shí)間線上來(lái)回跳轉(zhuǎn)。為了使之成為可能,我們大量地使用異步操作,因?yàn)? JavaScript 中沒(méi)有比這更好的替代選擇了。 有了 WebAssembly,我們就可以把大量的數(shù)據(jù)計(jì)算和渲染的工作移交給更加合適的語(yǔ)言來(lái)進(jìn)行處理而把數(shù)據(jù)收集和 DOM 操作交給 JavaScript 進(jìn)行處理。 番外篇打開(kāi) webassembly 官網(wǎng)就可以在頭部醒目地看到顯示它兼容的瀏覽器。分別是火孤,Chrome,Safari,IE Edge。點(diǎn)開(kāi) learn more 可以查看到這是于 2017/2/28 達(dá)成一致推出瀏覽器預(yù)覽版?,F(xiàn)在各項(xiàng)工作開(kāi)始進(jìn)入實(shí)施階段了,相信在未來(lái)的某個(gè)時(shí)刻就可以在生產(chǎn)環(huán)境使用它了。官網(wǎng)上面介紹了一個(gè) JavaScript 的子集 asm.js。另外,這里有一個(gè) WebAssembly 和 JavaScript 進(jìn)行性能比對(duì)的測(cè)試網(wǎng)站。
|
|
來(lái)自: 太極混元天尊 > 《學(xué)習(xí)資料》