摘要:本文是圖說(shuō)系列文章的第五篇。這樣的話,使用的開(kāi)發(fā)者也不需要做任何適配,但是它們卻能獲得更高性能。該圖并不是用來(lái)準(zhǔn)確的衡量其性能的。運(yùn)行編寫(xiě)出高性能的代碼是可能的。這種清理工作由引擎自動(dòng)進(jìn)行,稱(chēng)為垃圾回收。
本文是圖說(shuō) WebAssembly 系列文章的第五篇。如果您還未閱讀之前的文章,建議您從第一篇入手。
在上一篇文章中,我們說(shuō)到了使用 WebAssembly 和 JavaScript 并不是兩選一的選擇。我們并不希望太多開(kāi)發(fā)者只使用 WebAssembly 。
我們希望開(kāi)發(fā)者可以把部分 JavaScript 代碼替換為 WebAssembly 。
例如,React 團(tuán)隊(duì)可以把虛擬 DOM 改用 WebAssembly 來(lái)實(shí)現(xiàn)。這樣的話,使用 React 的開(kāi)發(fā)者也不需要做任何適配,但是它們卻能獲得更高性能。
能夠促使 React 團(tuán)隊(duì)這么做的原因最可能是 WebAssembly 的高性能。但是到底是什么使它有高性能呢?
JS 性能分析在我們理解 JavaScript 和 WebAssembly 之間的性能差異原因之前,我們需要先理解 JavaScript 引擎所做的工作。
下圖給了一個(gè)粗糙的描述,概括了當(dāng)前 JS 應(yīng)用的啟動(dòng)性能。
JS 引擎在這些任務(wù)上所耗費(fèi)的時(shí)間取決于頁(yè)面所用的 JS 代碼。該圖并不是用來(lái)準(zhǔn)確的衡量其性能的。相反,它是一種高度抽象的模型,用來(lái)比較實(shí)現(xiàn)相同功能的 JavaScript 和 WebAssembly 之間的性能差異。
圖中的每一塊表示該任務(wù)所耗費(fèi)的時(shí)間。
解析:把 JavaScript 源碼解析為解釋器能夠運(yùn)行的代碼的時(shí)間。
編譯+優(yōu)化:基準(zhǔn)編譯器和優(yōu)化編譯器所耗費(fèi)的時(shí)間。優(yōu)化編譯器的部分優(yōu)化工作并不是在主線程上進(jìn)行的,這部分耗費(fèi)的時(shí)間不包含在這里。
重新優(yōu)化:當(dāng)假設(shè)不成立時(shí),JIT 作出重新調(diào)整所耗費(fèi)的時(shí)間,包括重新優(yōu)化和回退到基準(zhǔn)代碼的時(shí)間。
執(zhí)行:運(yùn)行代碼耗費(fèi)的時(shí)間。
垃圾回收:清理內(nèi)存耗費(fèi)的時(shí)間。
要注意的是,這些過(guò)程并不會(huì)以離散塊或者特定的順序發(fā)生。相反,它們是交叉進(jìn)行的。
可能會(huì)解析完一小段,就會(huì)運(yùn)行一段,然后編譯一段;接著解析更多代碼,然后執(zhí)行更多代碼等等。
這種分段交叉進(jìn)行的設(shè)計(jì)相比早期的 JavaScript 來(lái)說(shuō)是一種很大的性能提升,早期的 JavaScript 執(zhí)行更像是下圖中的情形。
在最開(kāi)始的時(shí)候,只有解析器來(lái)跑 JavaScript ,執(zhí)行速度是相當(dāng)慢的。當(dāng)引入 JIT 后,執(zhí)行速度得到了大幅提升。
當(dāng)然,引入 JIT 的代價(jià)就是在監(jiān)視器和編譯器上投入了更多資源。
如果開(kāi)發(fā)者還是按照以前的方式來(lái)編寫(xiě) JavaScript 應(yīng)用,那么其實(shí)解析和編譯時(shí)間是很小的。
只不過(guò)隨著性能提升,開(kāi)發(fā)者開(kāi)發(fā)出了更大型的 JavaScript 應(yīng)用,對(duì)性能要求又變高了。
因此,性能還是有提升空間的。
WebAssembly 性能分析下圖是與典型網(wǎng)頁(yè)應(yīng)用相比時(shí),WebAssembly 的大致過(guò)程。
不同瀏覽器的處理可能略有不同,下面我們以 SpiderMonkey 引擎為例來(lái)說(shuō)明各個(gè)過(guò)程。
加載加載這部分并沒(méi)有體現(xiàn)在上圖中,但這部分所耗費(fèi)的時(shí)間就是從服務(wù)器下載文件的時(shí)間。
因?yàn)?WebAssembly 代碼比 JavaScript 代碼更加的精簡(jiǎn),所以加載 WebAssembly 文件是更快的。
盡管壓縮算法能夠極大減小 JavaScript 代碼的體積,但是 WebAssembly 壓縮后的二進(jìn)制代碼仍然比它要小。
這就意味著下載將耗費(fèi)更少時(shí)間,尤其是在低網(wǎng)速情況下。
解析JavaScript 代碼一旦下載到瀏覽器,它會(huì)被解析為抽象語(yǔ)法樹(shù)(AST)。
瀏覽器通常采用的策略是惰性處理,即只解析真正被用到的代碼以及只為還沒(méi)被調(diào)用的函數(shù)創(chuàng)建存根。
之后,AST 被轉(zhuǎn)化為引擎相關(guān)的中間代碼:字節(jié)碼(Bytecode)。
而 WebAssembly 則不需要這種轉(zhuǎn)換,因?yàn)樗旧硪呀?jīng)是一種中間代碼了。它只需要經(jīng)過(guò)解碼,并且驗(yàn)證解碼沒(méi)有發(fā)生錯(cuò)誤即可。
編譯+優(yōu)化正如前面關(guān)于 JIT 的文章所說(shuō),JavaScript 的編譯時(shí)發(fā)生在代碼運(yùn)行期間的。根據(jù)運(yùn)行時(shí)所用的不同數(shù)據(jù)類(lèi)型,相同代碼可能需要被編譯為多種代碼。
不同的瀏覽器編譯 WebAssembly 時(shí)使用不同方式。一些瀏覽器會(huì)在運(yùn)行代碼前先進(jìn)行基準(zhǔn)編譯,其他瀏覽器則會(huì)使用 JIT 。
但不管是哪種方式,WebAssembly 都是從里機(jī)器碼比較近的地方開(kāi)始的。比如說(shuō),程序本身就包含了數(shù)據(jù)的類(lèi)型信息,這樣的話就會(huì)有更高的性能,因?yàn)椋?/p>
編譯器再進(jìn)行優(yōu)化編譯之前不需要耗費(fèi)時(shí)間來(lái)檢查數(shù)據(jù)類(lèi)型
編譯器并不需要基于不同類(lèi)型來(lái)編譯出相同代碼的不同類(lèi)型版本代碼
有很多優(yōu)化已經(jīng)在 LLVM 之前完成了,所以這里可以減少編譯和優(yōu)化的開(kāi)銷(xiāo)
重新優(yōu)化有時(shí)候 JIT 必須丟棄之前已經(jīng)優(yōu)化的代碼并且重新編譯。
這種情況就發(fā)生在 JIT 之前的假設(shè)都不成立時(shí)。比如說(shuō),當(dāng)循環(huán)中使用了與之前不一樣的變量類(lèi)型,或者原型鏈上新增了一個(gè)函數(shù)。
這種去優(yōu)化帶來(lái)了兩個(gè)性能損耗。一是,JIT 需要丟棄已編譯的代碼并回退到基準(zhǔn)代碼;二是,如果這段代碼仍然會(huì)被調(diào)用很多次,那么又得重新花費(fèi)時(shí)間去再次優(yōu)化它。
而在 WebAssembly 中,數(shù)據(jù)類(lèi)型是很明確的,所以 JIT 不需要對(duì)運(yùn)行時(shí)的數(shù)據(jù)類(lèi)型做任何假設(shè)。也就意味著,它不不存在重新優(yōu)化可能。
運(yùn)行編寫(xiě)出高性能的 JavaScript 代碼是可能的。為此,你需要知道 JIT 是如何做優(yōu)化的。
比如,你需要知道如何寫(xiě)出讓編譯器特定化數(shù)據(jù)類(lèi)型的代碼。
但是,大多數(shù)開(kāi)發(fā)者并不知道 JIT 的內(nèi)部實(shí)現(xiàn)。即便是了解 JIT 內(nèi)部實(shí)現(xiàn)的人,也很難直接擊中要害。
許多我們?yōu)榱俗尨a更具可讀性的編程模式(比如抽出公共函數(shù)來(lái)處理多種類(lèi)型)反而阻礙了編譯器的優(yōu)化。
而且,不同瀏覽器的 JIT 所采用的各種優(yōu)化手段是不同的,這就導(dǎo)致了可能在某款瀏覽器上是最優(yōu)的,但是在另一款瀏覽器中則是很差的。
正因?yàn)檫@個(gè),運(yùn)行 WebAssembly 通常是更加快速的。許多 JIT 做的優(yōu)化在 WebAssembly 中根本不存在。
此外,WebAssembly 是被設(shè)計(jì)為一個(gè)編譯目標(biāo)的。也就是說(shuō),它是被用來(lái)作為編譯器輸出的,而不是用來(lái)供開(kāi)發(fā)者編碼的。
因?yàn)殚_(kāi)發(fā)者不需要直接對(duì) WebAssembly 編碼,所以它能夠使用更適合機(jī)器的指令,而這些指令通常能做到 10% ~ 800% 的性能提升。
垃圾回收在 JavaScript 中,開(kāi)發(fā)者并不需要專(zhuān)門(mén)去清理那些不再使用的變量所占用的內(nèi)存。這種清理工作由 JavaScript 引擎自動(dòng)進(jìn)行,稱(chēng)為垃圾回收(Garbage Collection)。
但是,如果你想要得到可預(yù)期的性能,這可能會(huì)成為阻礙。
你并不能控制什么時(shí)候進(jìn)行垃圾回收,所以它隨時(shí)可能發(fā)生。盡管大多數(shù)瀏覽器在垃圾回收的調(diào)度方面做的相當(dāng)不錯(cuò),但是它仍然可能阻礙你的代碼運(yùn)行。
至少現(xiàn)在,WebAssembly 根本不支持垃圾回收。所有內(nèi)存都是手動(dòng)管理的。
雖然這樣會(huì)讓編碼變得更加困難,但是它也讓性能變得更加穩(wěn)定。
所以,WebAssembly 之所以比 JavaScript 擁有更好的性能,是因?yàn)橐韵略颍?/p>
更精簡(jiǎn)的體積讓加載 WebAssembly 耗費(fèi)更少時(shí)間
解碼 WebAssembly 比解析 JavaScript 更快
編譯和優(yōu)化節(jié)省了時(shí)間開(kāi)銷(xiāo),因?yàn)?WebAssembly 比 JavaScript 更接近機(jī)器碼,而且 WebAssembly 是已經(jīng)提前做過(guò)優(yōu)化的
不會(huì)發(fā)生重新優(yōu)化的過(guò)程,因?yàn)?WebAssembly 自帶數(shù)據(jù)類(lèi)型和其他信息
代碼的運(yùn)行耗費(fèi)更好時(shí)間,因?yàn)樗鼪](méi)有那么多編譯器陷阱,而且它的指令集對(duì)機(jī)器更友好
不存在垃圾回收的過(guò)程,因?yàn)樗鞘謩?dòng)管理內(nèi)存的
以上就是為什么在大多數(shù)時(shí)候,WebAssembly 都比 JavaScript 性能好的原因。
當(dāng)然,WebAssembly 也存在表現(xiàn)并不如期望的那樣好的時(shí)候。同時(shí),也有一些正在進(jìn)行的改變使得它變得更快。這些我們會(huì)在下一篇中討論。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/94758.html
摘要:性能簡(jiǎn)史在年,被創(chuàng)造出來(lái)時(shí)并不是沖著性能去的。而且在之后的十年發(fā)展中,它的性能一直是很低的。的引入成就了性能提升的一個(gè)轉(zhuǎn)折點(diǎn),其執(zhí)行速度比以往快了之多。性能提升也使得在全新的問(wèn)題上使用成為可能?,F(xiàn)在,極可能是下一個(gè)性能轉(zhuǎn)折點(diǎn)。 你可能已經(jīng)聽(tīng)說(shuō) WebAssembly 代碼跑起來(lái)非???。但是你知道這是為什么嗎?在本系列文章中,我們將探究其原因。 何為 WebAssembly WebAss...
摘要:現(xiàn)狀年月日,主流的四大瀏覽器達(dá)成了共識(shí)并宣布的最小可行產(chǎn)品已經(jīng)完成。更快的函數(shù)調(diào)用當(dāng)前,在中調(diào)用函數(shù)比想象的要慢。直接操作目前,沒(méi)有任何方式能夠操作。這就導(dǎo)致了部分應(yīng)用可能會(huì)因此而推遲發(fā)布時(shí)間。結(jié)束現(xiàn)如今已經(jīng)相當(dāng)快速。 本文是圖說(shuō) WebAssembly 系列文章的最后一篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 現(xiàn)狀 2017 年 2 月 28 日,主流的四大瀏覽器達(dá)成了共識(shí)...
摘要:編譯器優(yōu)缺點(diǎn)與解釋器相比,編譯器有著相反的優(yōu)缺點(diǎn)。它們?yōu)橐嫘略隽艘粋€(gè)組件,稱(chēng)為監(jiān)視器,或者。優(yōu)化編譯器會(huì)基于監(jiān)視器記錄的代碼運(yùn)行信息來(lái)作出一些判斷。通常來(lái)說(shuō),優(yōu)化編譯器會(huì)使得代碼跑的更快。而這正是優(yōu)化編譯器所做的優(yōu)化之一。 本文是圖說(shuō) WebAssembly 系列文章的第二篇,如果你還沒(méi)閱讀其它的,建議您從第一篇開(kāi)始。 JavaScript 的運(yùn)行,一開(kāi)始是很慢的,但是后面會(huì)變得越來(lái)...
摘要:為了更好的理解,我們有必要去先理解什么是匯編,以及編譯器是如何產(chǎn)生匯編的。什么是匯編現(xiàn)在,我們來(lái)看看外星人的大腦是如何工作的。這些注釋就是匯編,也稱(chēng)為符號(hào)機(jī)器碼。結(jié)束以上的內(nèi)容就是什么是匯編以及它是如何從高級(jí)編程語(yǔ)言翻譯過(guò)來(lái)的。 本文是圖說(shuō) WebAssembly 系列文章的第三篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 為了更好的理解 WebAssembly ,我們有必要去先...
摘要:本文是圖說(shuō)系列文章的第四篇。它們表示一種可以在普遍流行機(jī)器上高效使用的指令集合。這是因?yàn)槭且环N稱(chēng)為堆棧機(jī)器。盡管是根據(jù)堆棧機(jī)器來(lái)設(shè)計(jì)的,但是這并不是它在真實(shí)物理機(jī)器上工作的方式。這些內(nèi)容稱(chēng)為段。 本文是圖說(shuō) WebAssembly 系列文章的第四篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 WebAssembly 是一種使得除 JavaScript 以外的編程語(yǔ)言也能運(yùn)行在網(wǎng)頁(yè)上...
閱讀 3053·2021-11-23 09:51
閱讀 3834·2021-11-22 15:29
閱讀 3294·2021-10-08 10:05
閱讀 1620·2021-09-22 15:20
閱讀 1042·2019-08-30 15:56
閱讀 1136·2019-08-30 15:54
閱讀 788·2019-08-26 11:54
閱讀 2690·2019-08-26 11:32