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