V8是怎么快速地解析JavaScript延遲解析的

本篇內(nèi)容介紹了“V8是怎么快速地解析JavaScript延遲解析的”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

創(chuàng)新互聯(lián)建站主營千山網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,重慶APP軟件開發(fā),千山h5小程序制作搭建,千山網(wǎng)站營銷推廣歡迎千山等地區(qū)企業(yè)咨詢

解析是將源代碼轉(zhuǎn)換成一個中間表示形式供編譯器使用的步驟(在V8中,是字節(jié)碼編譯器Ignition)。解析和編譯發(fā)生在web頁面啟動的關(guān)鍵路徑上,在啟動期間,并不是所有提供給瀏覽器的函數(shù)都需要被調(diào)用。盡管開發(fā)人員可以使用異步和延遲腳本來延遲這些代碼的加載,但這并不總是可行的。此外,許多web頁面的代碼只能被特定的特性使用,這樣一來,在每個頁面單獨(dú)運(yùn)行期間,用戶是根本無法訪問這些代碼的。

急切地編譯不必要的代碼會產(chǎn)生實際的資源成本:

  • 創(chuàng)建這些不必要的代碼會占用CPU的一部分時間,這會導(dǎo)致啟動時實際需要的代碼延遲加載。

  • 代碼對象會占用內(nèi)存,至少在回收機(jī)制判定當(dāng)前代碼不再需要并允許垃圾收集器回收之前是這樣的。

  • ***腳本結(jié)束執(zhí)行時編譯的代碼最終會緩存在磁盤上,占用磁盤空間。

由于這些原因,所有主流瀏覽器都實現(xiàn)了延遲解析。以前的做法是為每個函數(shù)生成一個抽象語法樹(AST),然后將其編譯為字節(jié)碼,而使用了延遲解析之后,解析器就可以“預(yù)解析”它遇到的函數(shù),而不需要對這些函數(shù)進(jìn)行完全解析。它通過切換到預(yù)解析器來實現(xiàn)這一點(diǎn),而預(yù)解析器是解析器的一個副本,它只做最基本的工作,否則就會跳過該函數(shù)。預(yù)解析器驗證它跳過的函數(shù)在語法上是否是有效的,并生成正確編譯外部函數(shù)所需的所有信息。在后邊調(diào)用預(yù)解析的函數(shù)時,將按需對其進(jìn)行完全解析和編譯。

變量分配

使預(yù)解析復(fù)雜化的主要問題是變量分配。

出于性能原因,函數(shù)激活是在機(jī)器堆棧上進(jìn)行管理的。例如,如果函數(shù)g調(diào)用了參數(shù)為1和2的函數(shù)f:

V8是怎么快速地解析JavaScript延遲解析的

首先將接收器(即f的this值,由于它是一個草率的函數(shù)調(diào)用,所以它是globalThis)推入堆棧,接著是被調(diào)用的函數(shù)f。然后再將參數(shù)1和2推入堆棧。此時函數(shù)f被調(diào)用。為了執(zhí)行調(diào)用,我們首先將g的狀態(tài)保存在堆棧上:  包括f的“返回指令指針”(rip;我們需要返回什么代碼)以及“幀指針”(fp;返回時堆棧應(yīng)該是什么樣子的)。然后我們輸入f,它為局部變量c分配空間,以及它可能需要的任何臨時空間。這確保了當(dāng)函數(shù)激活超出作用域時,函數(shù)使用的任何數(shù)據(jù)都會消失:  它只是從堆棧中彈出。

V8是怎么快速地解析JavaScript延遲解析的

對帶有參數(shù)a,b和局部變量c的函數(shù)f的調(diào)用的堆棧分配布局。

這種設(shè)置的問題是函數(shù)可以引用在外部函數(shù)中聲明的變量。內(nèi)部函數(shù)存活的時間可能會比它們被創(chuàng)建時的激活時間要長:

V8是怎么快速地解析JavaScript延遲解析的

在上面的例子中,從inner到make_f中聲明的變量d的引用會在make_f返回后進(jìn)行計算。為了實現(xiàn)這一點(diǎn),使用詞法閉包的語言的虛擬機(jī)會在一個稱為“上下文”的結(jié)構(gòu)中分配從堆上的內(nèi)部函數(shù)中引用的變量。

V8是怎么快速地解析JavaScript延遲解析的

通過將make_f的參數(shù)復(fù)制到一個上下文中來對它進(jìn)行調(diào)用,該調(diào)用的堆棧布局會在堆上進(jìn)行分配,供捕捉d的inner稍后使用。

這意味著對于函數(shù)中聲明的每個變量,我們需要知道內(nèi)部函數(shù)是否引用了該變量,以便決定是在棧上分配該變量,還是在堆上分配的上下文中分配該變量。當(dāng)我們計算一個函數(shù)的字面量時,我們分配一個閉包,它指向函數(shù)的代碼和當(dāng)前上下文:  包含函數(shù)可能需要訪問的變量值的對象。

長話短說,我們至少需要跟蹤預(yù)解析器中的變量引用。

如果我們只跟蹤引用,就會過多估計引用的變量。在外部函數(shù)中聲明的變量可以通過內(nèi)部函數(shù)中的重新聲明來隱藏,從而創(chuàng)建一個來自該內(nèi)部函數(shù)的引用,并將其指向內(nèi)部聲明,而不是外部聲明。如果我們無條件地在上下文中分配外部變量,程序性能就會受到影響。因此,要使變量分配能正確地處理預(yù)解析過程,我們需要確保預(yù)解析后的函數(shù)正確地跟蹤變量引用和聲明。

頂層代碼是這條規(guī)則的一個例外。一個腳本的頂層總是堆分配的,因為變量在腳本之間是可見的。接近良好工作的體系結(jié)構(gòu)的一個簡單方法是簡單地運(yùn)行預(yù)解析器,而不需要對快速解析的頂層函數(shù)進(jìn)行變量跟蹤;并為內(nèi)部函數(shù)使用完整的解析器,但在編譯的時候跳過它們。這比預(yù)解析過程成本更高,因為我們不需要構(gòu)建整個AST,但它使我們啟動并運(yùn)行。這正是V8在新版本V8  v6.3 / Chrome 63中所做的。

向預(yù)解析器說明變量的情況

跟蹤預(yù)解析器中的變量聲明和引用是非常復(fù)雜的,因為在JavaScript中,某些部分表達(dá)式的含義從一開始就不清楚。例如,假設(shè)我們有一個帶參數(shù)d的函數(shù)f,它有一個內(nèi)部函數(shù)g,從表達(dá)式看起來g可能引用了d。

V8是怎么快速地解析JavaScript延遲解析的

它最終可能確實會引用d,因為我們看到的tokens標(biāo)記是析構(gòu)賦值表達(dá)式的一部分。

V8是怎么快速地解析JavaScript延遲解析的

它最終也可能是一個帶有析構(gòu)參數(shù)d的箭頭函數(shù),在這種情況下,f中的d就沒有被g引用。

V8是怎么快速地解析JavaScript延遲解析的

最初,我們的預(yù)解析器是作為解析器的獨(dú)立副本實現(xiàn)的,沒有太多的共享,這導(dǎo)致兩個解析器會隨著時間的推移而產(chǎn)生分歧。通過將解析器和預(yù)解析器重寫為基于實現(xiàn)了奇異遞歸模板模式的ParserBase,我們成功地***化了共享,同時也保留了單獨(dú)副本的性能優(yōu)勢。這大大簡化了向預(yù)解析器添加全部變量跟蹤的工作,因為這個實現(xiàn)的大部分內(nèi)容可以在解析器和預(yù)解析器之間共享。

實際上,忽略變量聲明和頂層函數(shù)的引用是不正確的。ECMAScript規(guī)范要求在***次解析腳本時要檢測各種類型的變量沖突。例如,如果一個變量在同一作用域內(nèi)被兩次聲明為詞法變量,則被認(rèn)為是early  SyntaxError。因為我們的預(yù)解析器只是跳過了變量聲明,所以在預(yù)解析過程中它將允許代碼錯誤地運(yùn)行。此時我們認(rèn)為性能上的勝利使對規(guī)范的違反情有可原?,F(xiàn)在預(yù)解析器  能正確地跟蹤變量,盡管如此,我們還是應(yīng)該在沒有明顯性能代價的情況下消除這類與變量解析相關(guān)的違反規(guī)范的行為。

跳過內(nèi)部函數(shù)

如前所述,當(dāng)***次調(diào)用一個預(yù)解析的函數(shù)時,我們將對其進(jìn)行完全解析,并將生成的AST編譯為字節(jié)碼。

V8是怎么快速地解析JavaScript延遲解析的

該函數(shù)直接指向外部上下文,其中包含內(nèi)部函數(shù)需要使用的變量聲明的值。為了允許函數(shù)的延遲編譯(并支持調(diào)試器),上下文會指向一個名為ScopeInfo的元數(shù)據(jù)對象。ScopeInfo對象描述了上下文中列出的變量。這意味著在編譯內(nèi)部函數(shù)時,我們可以計算變量在上下文鏈中的位置。

但是,要計算延遲編譯的函數(shù)本身是否需要上下文,我們需要再次執(zhí)行范圍解析:  我們需要知道嵌套在延遲編譯的函數(shù)中的函數(shù)是否引用了由延遲函數(shù)聲明的變量。我們可以通過重新解析這些函數(shù)來計算出來。這正是V8在升級到V8v6.3/Chrome63之前所做的。但是,這并不是理想的性能***的方法,因為它使資源大小和解析成本之間的關(guān)系變成非線性:  我們將盡可能多地解析嵌套函數(shù)。除了動態(tài)程序的自然嵌套之外,JavaScript打包器通常用“即時調(diào)用函數(shù)表達(dá)式”(IIFEs)的方式來包裝代碼,這使得大多數(shù)JavaScript程序具有多個嵌套層。

V8是怎么快速地解析JavaScript延遲解析的

每次重新解析至少會增加解析函數(shù)的成本。

為了避免非線性性能開銷,我們甚至在預(yù)解析過程中執(zhí)行全作用域解析。我們存儲了足夠的元數(shù)據(jù),這樣我們稍后就可以簡單地跳過內(nèi)部函數(shù),而不必重新解析它們。一種方法是存儲由內(nèi)部函數(shù)引用的變量名。這樣做的存儲成本很高,并要求我們?nèi)匀贿M(jìn)行重復(fù)工作:我們已經(jīng)在預(yù)解析期間執(zhí)行了變量解析。

相反,我們將在變量分配的地方將每一個變量序列化為它的一個密集標(biāo)記數(shù)組。當(dāng)我們延遲解析一個函數(shù)時,變量按照預(yù)解析器看到的順序被重新創(chuàng)建,我們可以簡單地將元數(shù)據(jù)應(yīng)用于這些變量。現(xiàn)在函數(shù)已經(jīng)編譯完成,已經(jīng)不再需要變量分配元數(shù)據(jù)了,這樣它就可以被當(dāng)做垃圾進(jìn)行回收。由于我們只需要這個元數(shù)據(jù)來處理實際包含內(nèi)部函數(shù)的函數(shù),所以大部分函數(shù)甚至不需要這個元數(shù)據(jù),從而顯著地降低了內(nèi)存開銷。

V8是怎么快速地解析JavaScript延遲解析的

通過跟蹤預(yù)解析的函數(shù)的元數(shù)據(jù),我們可以完全跳過內(nèi)部函數(shù)。

跳過內(nèi)部函數(shù)的性能影響是非線性的,就像重新預(yù)解析內(nèi)部函數(shù)的開銷一樣。有些站點(diǎn)將它們的所有函數(shù)都提升到了頂層范圍。因為它們的嵌套層數(shù)總是0,所以開銷也總是0。然而,許多現(xiàn)代的站點(diǎn)實際上都有許多深層嵌套函數(shù)。當(dāng)V8  v6.3 / Chrome 63啟動該特性時,我們就會在這些站點(diǎn)上看到顯著的改進(jìn)。啟用該特性的主要優(yōu)點(diǎn)是,現(xiàn)在代碼的嵌套深度已經(jīng)無關(guān)緊要:  任何函數(shù)最多只預(yù)解析一次,完全解析一次[1]。

V8是怎么快速地解析JavaScript延遲解析的

主線程和非主線程的解析時間,以及運(yùn)行“跳過內(nèi)部函數(shù)”前后都得到了優(yōu)化。

隨時調(diào)用函數(shù)表達(dá)式

如前所述,打包器通常通過將模塊代碼封裝在一個它們即時調(diào)用的閉包中,來將多個模塊組合到一個文件中。這為模塊提供了隔離,允許它們像腳本中唯一的代碼一樣運(yùn)行。這些函數(shù)本質(zhì)上是嵌套的腳本;腳本執(zhí)行時這些函數(shù)會立即被調(diào)用。打包器通常以帶圓括號的函數(shù),即  (function(){…})(),的形式提供即時調(diào)用函數(shù)表達(dá)式(IIFEs,發(fā)音為“iffies”)。

由于這些函數(shù)在腳本執(zhí)行期間是立即需要的,所以預(yù)解析這些函數(shù)并不理想。在腳本的頂層執(zhí)行過程中,我們急需這些函數(shù)被編譯,所以我們會完全解析和編譯這些函數(shù)。這意味著,我們在前期解析越快,代碼運(yùn)行時啟動就越快,并且不會產(chǎn)生不必要的額外成本。

你可能會問,為什么不直接編譯調(diào)用的函數(shù)呢?雖然開發(fā)人員在一個函數(shù)被調(diào)用時能很容易注意到它,但是對于解析器情況則不同。解析器在開始解析函數(shù)之前需要決定該函數(shù)是需要立即編譯還是推遲編譯。語法中存在的歧義使得簡單地快速掃描到函數(shù)末尾變得很困難,而且成本很快就與常規(guī)預(yù)解析的成本一樣。

因此V8有兩個簡單的模式,它可以將函數(shù)識別為隨時調(diào)用函數(shù)表達(dá)式(PIFEs,發(fā)音為“piffies”),這樣它會快速解析并編譯一個函數(shù):

如果一個函數(shù)是一個帶圓括號的函數(shù)表達(dá)式,即(function(){…}),我們假設(shè)它將被調(diào)用。我們一看到這個模式的開始,即(function,就立即做出這個假設(shè)。

在V8 v5.7 / Chrome  57中我們也檢測了由UglifyJS生成的模式!function(){…}(),function(){…}(),function(){…}()。一旦我們看到!function或者function后面如果緊跟著一個PIFE,那么這個檢測就起作用了。

由于V8會立即編譯PIFEs,所以它們可以被用作配置文件導(dǎo)向的反饋[2],通知瀏覽器啟動需要哪些函數(shù)。

當(dāng)V8還在預(yù)解析內(nèi)部函數(shù)時,一些開發(fā)人員已經(jīng)注意到JS解析對啟動的影響相當(dāng)大。optimize-js包會基于靜態(tài)啟發(fā)式將函數(shù)轉(zhuǎn)換為PIFEs。這個包的創(chuàng)建對V8的負(fù)載性能有很大的影響。通過在V8  v6.1上運(yùn)行optimize-js提供的基準(zhǔn)測試,我們復(fù)制了這些結(jié)果,你只需要查看縮小的腳本。

V8是怎么快速地解析JavaScript延遲解析的

急切地解析和編譯PIFEs會導(dǎo)致冷啟動和熱啟動稍微快一些 (***和第二頁加載,測量總的解析+編譯+執(zhí)行時間)。但是,由于對解析器的顯著改進(jìn),這在V8  v7.5上的好處要比在V8 v6.1上使用的好處小得多。

盡管如此,但我們現(xiàn)在不再需要重新解析內(nèi)部函數(shù),而且由于解析器變得更快,通過optimize-js獲得的性能改進(jìn)也大大降低。實際上,v7.5的默認(rèn)配置已經(jīng)比運(yùn)行在v6.1上的優(yōu)化版本快得多。即使在v7.5中,對于啟動期間需要的代碼,少量使用PIFEs仍然很有用:  我們避免了預(yù)解析,因為我們很早就知道會需要這個函數(shù)。

盡管如此,但我們現(xiàn)在不再需要重新解析內(nèi)部函數(shù),而且由于解析器變得更快,通過optimize-js獲得的性能改進(jìn)也大大降低。實際上,v7.5的默認(rèn)配置已經(jīng)比運(yùn)行在v6.1上的優(yōu)化版本快得多。即使在v7.5中,對于啟動期間需要的代碼,少量使用PIFEs仍然很有用: 我們避免了預(yù)解析,因為我們很早就知道會需要這個函數(shù)。

optimize-js基準(zhǔn)測試結(jié)果并不能準(zhǔn)確地反映實際情況。腳本是同步加載的,整個解析+編譯時間都被計入加載時間。在實際環(huán)境中,你可能會使用<script>標(biāo)記來加載腳本。這使得Chrome的預(yù)加載器能夠在腳本被計算之前就發(fā)現(xiàn)它,并在不阻塞主線程的情況下下載、解析和編譯該腳本。我們決定急切地編譯的所有東西都是在主線程之外自動編譯的,這樣就會確保計入啟動時間的值最小化。使用非主線程腳本編譯來運(yùn)行會放大使用PIFEs的影響。

但是,這樣做仍然有成本,特別是內(nèi)存成本,所以急切地編譯所有東西并不是一個好主意:

V8是怎么快速地解析JavaScript延遲解析的

急切地編譯所有JavaScript會付出巨大的內(nèi)存代價。

雖然在啟動期間為需要的函數(shù)添加圓括號是一個好主意(例如,基于配置的啟動),但是使用像optimize-js這樣的包來應(yīng)用簡單的靜態(tài)啟發(fā)式并不是一個好主意。例如,它假設(shè)一個函數(shù)在啟動期間被調(diào)用,如果它是一個函數(shù)調(diào)用的參數(shù)。但是,如果這樣一個函數(shù)實現(xiàn)了一個只需要很長時間的完整模塊,那么最終會編譯太多。過于急切地編譯對性能沒有好處: 沒有延遲編譯的V8會顯著地降低加載時間。此外,當(dāng)UglifyJS和其它minifiers(最小化器)從不是IIFEs的PIFEs中刪除括號時,也就刪除了本可以應(yīng)用于通用模塊定義樣式模塊的有用提示,這樣一來,optimize-js的一些好處就帶來了問題。這可能是minifiers應(yīng)該修復(fù)的一個問題,以便在急切地編譯PIFEs的瀏覽器上獲得***的性能。

“V8是怎么快速地解析JavaScript延遲解析的”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

名稱欄目:V8是怎么快速地解析JavaScript延遲解析的
文章源于:http://bm7419.com/article10/igojdo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作、做網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)、虛擬主機(jī)電子商務(wù)、網(wǎng)站導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化