摘要:在執(zhí)行函數(shù)時,通過保存堆棧狀態(tài),再保存堆棧跳出后返回位置的指針,最后對變量賦值。這看上去沒有問題,只要將值存在堆棧就搞定了。
1. 引言
本周精讀的文章是 V8 引擎 Lazy Parsing,看看 V8 引擎為了優(yōu)化性能,做了怎樣的嘗試吧!
這篇文章介紹的優(yōu)化技術(shù)叫 preparser,是通過跳過不必要函數(shù)編譯的方式優(yōu)化性能。
2. 概述 & 精讀解析 Js 發(fā)生在網(wǎng)頁運行的關(guān)鍵路徑上,因此加速對 JS 的解析,就可以加速網(wǎng)頁運行效率。
然而并不是所有 Js 都需要在初始化時就被執(zhí)行,因此也不需要在初始化時就解析所有的 Js!因為編譯 Js 會帶來三個成本問題:
編譯不必要的代碼會占用 CPU 資源。
在 GC 前會占用不必要的內(nèi)存空間。
編譯后的代碼會緩存在磁盤,占用磁盤空間。
因此所有主流瀏覽器都實現(xiàn)了 Lazy Parsing(延遲解析),它會將不必要的函數(shù)進行預解析,也就是只解析出外部函數(shù)需要的內(nèi)容,而全量解析在調(diào)用這個函數(shù)時才發(fā)生。
預解析的挑戰(zhàn)本來預解析也不難,因為只要判斷一個函數(shù)是否會立即執(zhí)行就可以了,只有立即執(zhí)行的函數(shù)才需要被完全解析。
使得預解析變復雜的是變量分配問題。原文通過了堆棧調(diào)用的例子說明原因:
Js 代碼的執(zhí)行在堆棧上完成,比如下面這個函數(shù):
function f(a, b) { const c = a + b; return c; } function g() { return f(1, 2); // The return instruction pointer of `f` now points here // (because when `f` `return`s, it returns here). }
這段函數(shù)的調(diào)用堆棧如下:
需要創(chuàng)建一個 context 存儲函數(shù) f 中變量 d 的值。
也就是說,如果一個在函數(shù)內(nèi)部定義的變量被子 Scope 使用時,Js 引擎需要識別這種情況,并將這個變量值存儲在 context 中。
所以對于函數(shù)定義的每一個入?yún)ⅲ覀冃枰榔涫欠駮蛔雍瘮?shù)引用。也就是說,在 preparser 階段,我們只要少能分析出哪些變量被內(nèi)部函數(shù)引用了。
難以分辨的引用預處理器中跟蹤變量的申明與引用很復雜,因為 Js 的語法導致了無法從部分表達式推斷含義,比如下面的函數(shù):
function f(d) { function g() { const a = ({ d }
我們不清楚第三行的 d 到底是不是指代第一行的 d。它可能是:
function f(d) { function g() { const a = ({ d } = { d: 42 }); return a; } return g; }
也可能只是一個自定義函數(shù)參數(shù),與上面的 d 無關(guān):
function f(d) { function g() { const a = ({ d }) => d; return a; } return [d, g]; }惰性 parse
在執(zhí)行函數(shù)時,只會將最外層執(zhí)行的函數(shù)完全編譯并生成 AST,而對內(nèi)部模塊只進行 preparser。
// This is the top-level scope. function outer() { // preparsed function inner() { // preparsed } } outer(); // Fully parses and compiles `outer`, but not `inner`.
為了允許惰性編譯函數(shù),上下文指針指向了 ScopeInfo 的對象(從代碼中可以看到,ScopeInfo 包含上下文信息,比如當前上下文是否有函數(shù)名,是否在一個函數(shù)內(nèi)等等),當編譯內(nèi)部函數(shù)時,可以利用 ScopeInfo 繼續(xù)編譯子函數(shù)。
但是為了判斷惰性編譯函數(shù)自身是否需要一個上下文,我們需要再次解析內(nèi)部的函數(shù):比如我們需要知道某個子函數(shù)是否對外層函數(shù)定義的變量有所引用。
這樣就會產(chǎn)生遞歸遍歷:
由于代碼總會包含一些嵌套,而編譯工具更會產(chǎn)生 IIFE(立即調(diào)用函數(shù)) 這種多層嵌套的表達式,使得遞歸性能比較差。
而下面有一種辦法可以將時間復雜度簡化為線性:將變量分配的位置序列化為一個密集的數(shù)組,當惰性解析函數(shù)時,變量會按照原先的順序重新創(chuàng)建,這樣就不需要因為子函數(shù)可能引用外層定義變量的原因,對所有子函數(shù)進行遞歸惰性解析了。
按照這種方式優(yōu)化后的時間復雜度是線性的:
針對模塊化打包的優(yōu)化由于現(xiàn)代代碼幾乎都是模塊化編寫的,構(gòu)建起在打包時會將模塊化代碼封裝在 IIFE(立即調(diào)用的閉包)中,以保證模擬模塊化環(huán)境運行。比如 (function(){....})()。
這些代碼看似在函數(shù)中應該惰性編譯,但其實這些模塊化代碼從一開始就要被編譯,否則反而會影響性能,因此 V8 有兩種機制識別這些可能被立即調(diào)用的函數(shù):
如果函數(shù)是帶括號的,比如 (function(){...}),就假設它會被立即調(diào)用。
從 V8 v5.7 / Chrome 57 開始,還會識別 uglifyJS 的 !function(){...}(), function(){...}(), function(){...}() 這種模式。
然而在瀏覽器引擎解析環(huán)境比較復雜,很難對函數(shù)進行完整字符串匹配,因此只能對函數(shù)頭進行簡單判斷。所以對于下面這種匿名函數(shù)的行為,瀏覽器是不識別的:
// pre-parser function run(func) { func() } run(function(){}) // 在這執(zhí)行它,進行 full parser
上面的代碼看上去沒毛病,但由于瀏覽器只檢測被括號括住的函數(shù),因此這個函數(shù)不被認為是立即執(zhí)行函數(shù),因此在后續(xù)執(zhí)行時會被重復 full-parse。
也有一些代碼輔助轉(zhuǎn)換工具幫助 V8 正確識別,比如 optimize-js,會將代碼做如下轉(zhuǎn)換。
轉(zhuǎn)換前:
!function (){}() function runIt(fun){ fun() } runIt(function (){})
轉(zhuǎn)換后:
!(function (){})() function runIt(fun){ fun() } runIt((function (){}))
然而在 V8 v7.5+ 已經(jīng)很大程度解決了這個問題,因此現(xiàn)在其實不需要使用 optimize-js 這種庫了~
4. 總結(jié)JS 解析引擎在性能優(yōu)化做了不少工作,但同時也要應對代碼編譯器產(chǎn)生的特殊 IIFE 閉包,防止對這種立即執(zhí)行閉包進行重復 parser。
最后,不要試圖總是將函數(shù)用括號括起來,因為這樣會導致惰性編譯的特性無法啟用。
討論地址是:精讀《V8 引擎 Lazy Parsing》 · Issue #148 · dt-fe/weekly
如果你想?yún)⑴c討論,請 點擊這里,每周都有新的主題,周末或周一發(fā)布。前端精讀 - 幫你篩選靠譜的內(nèi)容。
關(guān)注 前端精讀微信公眾號
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/103766.html
摘要:更好的安全性隨著的發(fā)布,從升級到了,更安全且更易配置。通過使用,程序可以減少握手所需時間來提升請求性能。提供診斷報告有一項實驗功能,根據(jù)用戶需求提供診斷報告,包括崩潰性能下降內(nèi)存泄露使用高等等。前端精讀幫你篩選靠譜的內(nèi)容。 1. 引言 Node12 發(fā)布有幾個月了,讓我們跟隨 Nodejs 12 一起看看 Node12 帶來了哪些改變。 2. 概述 Node12 與以往的版本不同,帶來...
摘要:引言本周精讀的文章是,看看作者是如何解釋這個多態(tài)性含義的。讀完文章才發(fā)現(xiàn),文章標題改為的多態(tài)性更妥當,因為整篇文章都在說,而使用場景不局限于。更多討論討論地址是精讀的多態(tài)性如果你想?yún)⑴c討論,請點擊這里,每周都有新的主題,周末或周一發(fā)布。 1 引言 本周精讀的文章是:surprising-polymorphism-in-react-applications,看看作者是如何解釋這個多態(tài)性含...
摘要:概述的解釋器優(yōu)化器代碼可能在字節(jié)碼或者優(yōu)化后的機器碼狀態(tài)下執(zhí)行,而生成字節(jié)碼速度很快,而生成機器碼就要慢一些了。比如有一個函數(shù),從獲取值引擎生成的字節(jié)碼結(jié)構(gòu)是這樣的指令是獲取參數(shù)指向的對象,并存儲在,第二步則返回。 1 引言 本期精讀的文章是:JS 引擎基礎之 Shapes and Inline Caches 一起了解下 JS 引擎是如何運作的吧! JS 的運作機制可以分為 AST 分...
摘要:目前我們的業(yè)務項目采用的來進行優(yōu)化和首屏性能提升。可變性需要讓開發(fā)人員降低開發(fā)時的基準線,來保證每一個用戶的體驗。對于路由的切分以及庫的引入來說,這一個原則至關(guān)重要。快速生成一份站點的性能審查報告。 The Cost Of JavaScript 2018 關(guān)于原文 原文是在Medium上面看到的,Chrome工程師Addy Osmani發(fā)布的一篇文章,這位的Medium上面的自我介紹里...
摘要:調(diào)度系統(tǒng),支持不同渲染優(yōu)先級,對進行調(diào)度。調(diào)度帶來的限制調(diào)度系統(tǒng)也存在兩個問題。調(diào)度系統(tǒng)能力有限,只能在瀏覽器提供的能力范圍內(nèi)進行調(diào)度,而無法影響比如的渲染回收周期。精讀關(guān)于調(diào)度系統(tǒng)的剖析,可以讀深入剖析這篇文章,感謝我們團隊的淡蒼提供。 1. 引言 這次介紹的文章是 scheduling-in-react,簡單來說就是 React 的調(diào)度系統(tǒng),為了得到更順滑的用戶體驗。 畢竟前端做到...
閱讀 2232·2021-09-22 15:25
閱讀 3617·2019-08-30 12:48
閱讀 2205·2019-08-30 11:25
閱讀 2338·2019-08-30 11:05
閱讀 725·2019-08-29 17:28
閱讀 3284·2019-08-26 12:16
閱讀 2608·2019-08-26 11:31
閱讀 1701·2019-08-23 17:08