自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何將 DevTools的堆棧追蹤速度提高10倍

開發(fā) 前端
天分享的內(nèi)容是如何將Chrome DevTools的堆棧追蹤速度提高了10倍。之前聽工作6年的同事說(shuō),DevTools會(huì)影響頁(yè)面性能。正好逛國(guó)外社區(qū)的時(shí)候,發(fā)現(xiàn)這篇不錯(cuò)的文章,借此機(jī)會(huì)分享給大家。

[[399057]]

本文轉(zhuǎn)載自微信公眾號(hào)「TianTianUp」,作者小弋。轉(zhuǎn)載本文請(qǐng)聯(lián)系TianTianUp公眾號(hào)。

大家好,我是TianTian。

今天分享的內(nèi)容是如何將Chrome DevTools的堆棧追蹤速度提高了10倍。

之前聽工作6年的同事說(shuō),DevTools會(huì)影響頁(yè)面性能。正好逛國(guó)外社區(qū)的時(shí)候,發(fā)現(xiàn)這篇不錯(cuò)的文章,借此機(jī)會(huì)分享給大家。

正文

Web開發(fā)人員已經(jīng)開始期待在調(diào)試他們的代碼時(shí)幾乎沒(méi)有性能影響。然而,這種期望絕不是普遍的。一個(gè)C++開發(fā)人員永遠(yuǎn)不會(huì)期望他們的應(yīng)用程序的調(diào)試構(gòu)建能夠達(dá)到生產(chǎn)性能,而在Chrome瀏覽器的早期,僅僅是打開DevTools就會(huì)大大影響頁(yè)面的性能。

現(xiàn)在已經(jīng)感覺(jué)不到這種性能下降了,這是多年來(lái)對(duì)DevTools和V8的調(diào)試能力投資的結(jié)果。盡管如此,我們永遠(yuǎn)無(wú)法將DevTools的性能開銷降低到零。設(shè)置斷點(diǎn)、踏過(guò)代碼、收集堆棧痕跡、捕獲性能跟蹤等等,都會(huì)在不同程度上影響執(zhí)行速度。畢竟,觀察到的東西會(huì)改變它。

但當(dāng)然,DevTools的開銷--像任何調(diào)試器一樣--應(yīng)該是合理的。最近我們看到,在某些情況下,DevTools會(huì)拖慢應(yīng)用程序的速度,以至于它不能再使用的報(bào)告數(shù)量顯著增加。下面你可以看到一個(gè)來(lái)自報(bào)告chromium:1069425的并排比較,說(shuō)明了僅僅打開DevTools的性能開銷。

報(bào)告chromium:1069425鏈接點(diǎn)這里:

  1. https://bugs.chromium.org/p/chromium/issues/detail?id=1069425 

查看這個(gè)視頻:

正如你從視頻中看到的,速度降低了5-10倍,這顯然是不可接受的。

第一步是要了解所有的時(shí)間都去哪兒了,是什么導(dǎo)致了DevTools打開時(shí)的巨大速度下降。

在Chrome渲染器進(jìn)程上使用Linux perf,發(fā)現(xiàn)整個(gè)渲染器執(zhí)行時(shí)間的分布如下。

圖1

雖然我們有點(diǎn)期待看到與收集堆棧痕跡有關(guān)的東西,但我們不會(huì)想到,整個(gè)執(zhí)行時(shí)間的大約90%都用于符號(hào)化堆棧幀。

這里的符號(hào)化是指從原始堆棧幀中解析函數(shù)名和具體的源位置--腳本中的行號(hào)和列號(hào)的行為。

方法名推斷

更令人驚訝的是,幾乎所有的時(shí)間都流向了V8中的JSStackFrame::GetMethodName()函數(shù)。

盡管我們從以前的調(diào)查中知道,JSStackFrame::GetMethodName()在性能問(wèn)題的土地上并不陌生。

這個(gè)函數(shù)試圖為那些被認(rèn)為是方法調(diào)用的框架(代表obj.func()而不是func()形式的函數(shù)調(diào)用的框架)計(jì)算方法的名稱。

快速查看代碼發(fā)現(xiàn),它的工作原理是對(duì)對(duì)象及其原型鏈進(jìn)行全面的遍歷,并尋找:

  • 數(shù)據(jù)屬性,其值是func的閉包
  • 訪問(wèn)器屬性,其中g(shù)et或set等同于func閉包。

現(xiàn)在,雖然這本身聽起來(lái)并不特別便宜,但它也聽起來(lái)不像是能解釋這種可怕的減速。

因此,我們開始挖掘chromium:1069425中報(bào)告的例子,我們發(fā)現(xiàn)堆棧痕跡是為異步任務(wù)以及來(lái)自classes.js的日志信息收集的,這是一個(gè)10MB的JavaScript文件。

仔細(xì)觀察發(fā)現(xiàn),這基本上是一個(gè)Java運(yùn)行時(shí),加上編譯成JavaScript的應(yīng)用程序代碼。堆棧跟蹤包含了幾個(gè)框架,其中有一些方法被調(diào)用到一個(gè)對(duì)象A上,所以我們認(rèn)為可能值得了解我們正在處理的是哪種對(duì)象。

chromium:1069425 : https://bugs.chromium.org/p/chromium/issues/detail?id=1069425

圖2

顯然,從Java到JavaScript的編譯器產(chǎn)生了一個(gè)對(duì)象,上面有高達(dá)82,203個(gè)函數(shù)。

這顯然開始變得有趣了。接下來(lái)我們回到V8的JSStackFrame::GetMethodName(),以了解是否有一些低垂的果實(shí)可以被我們采摘。

  • 它的工作原理是首先將函數(shù)的 "名字 "作為對(duì)象的一個(gè)屬性進(jìn)行查找,如果找到了,則檢查該屬性的值是否與該函數(shù)相匹配。
  • 如果函數(shù)沒(méi)有名字,或者對(duì)象沒(méi)有匹配的屬性,它就會(huì)通過(guò)遍歷對(duì)象的所有屬性及其原型來(lái)進(jìn)行反向查找。

在我們的示例中,所有函數(shù)都是匿名的,并且具有空的“名稱”屬性。

  1. A.SDV = function() { 
  2.    // ... 
  3. }; 

最初的發(fā)現(xiàn)是將反向查找分為兩個(gè)步驟(針對(duì)對(duì)象本身及其原型鏈中的每個(gè)對(duì)象執(zhí)行):

  • 提取所有可枚舉屬性的名稱,然后
  • 對(duì)每個(gè)名稱執(zhí)行通用屬性查找,測(cè)試結(jié)果屬性值是否與我們要查找的閉包相匹配。

這看起來(lái)是一個(gè)低效的操作,因?yàn)樘崛∶中枰弑樗械膶傩?。與其做兩遍--O(N)的名字提取和O(N log(N))的測(cè)試,我們可以在單遍中完成所有工作,并直接檢查屬性值。這使得整個(gè)函數(shù)的速度提高了2-10倍左右。

第二個(gè)發(fā)現(xiàn)甚至更有趣。雖然這些函數(shù)在技術(shù)上是匿名函數(shù),但V8引擎還是為它們記錄了我們稱之為推斷的名稱。對(duì)于以obj.foo = function() {...}形式出現(xiàn)在賦值右側(cè)的函數(shù)字面,V8解析器會(huì)記住 "obj.foo "作為該函數(shù)字面的推斷名稱。

因此,在我們的例子中,雖然我們沒(méi)有可以直接查找的正確名稱,但我們確實(shí)有足夠接近的東西。對(duì)于上面的A.SDV = function() {...}例子,我們有 "A.SDV "作為推斷名稱,我們可以通過(guò)尋找最后一個(gè)點(diǎn)從推斷名稱中得出屬性名稱,然后去尋找對(duì)象上的屬性 "SDV"。

這幾乎在所有的情況下都起到了作用,用單一的屬性查找取代了昂貴的全面遍歷。這兩項(xiàng)改進(jìn)是CL的一部分,大大降低了chromium:1069425中報(bào)告的例子的速度下降。

錯(cuò)誤堆棧

我們本可以在這里收工了,但是有些事情是不對(duì)勁的,因?yàn)镈evTools從來(lái)不使用堆??蚣艿姆椒?。事實(shí)上,C++ API中的v8::StackFrame類甚至沒(méi)有提供獲取方法名稱的方法。因此,我們最終會(huì)首先調(diào)用JSStackFrame::GetMethodName(),這似乎是錯(cuò)誤的。

相反,我們使用(和公開)方法名稱的唯一地方是在JavaScript堆棧跟蹤API中。為了理解這種用法,請(qǐng)考慮下面這個(gè)簡(jiǎn)單的例子error-methodname.js:

  1. function foo() { 
  2.     console.log((new Error).stack); 
  3.  
  4. var object = {bar: foo}; 
  5. object.bar(); 

這里我們有一個(gè)函數(shù)foo,它被安裝在對(duì)象的名稱 "bar "下。在Chromium中運(yùn)行這個(gè)片段,會(huì)產(chǎn)生以下輸出:

  1. Error 
  2.     at Object.foo [as bar] (error-methodname.js:2) 
  3.     at error-methodname.js:6 

在這里,我們看到了方法名查找的作用。最上面的堆??蚣鼙伙@示為通過(guò)名為bar的方法在Object的實(shí)例上調(diào)用函數(shù)foo。所以非標(biāo)準(zhǔn)的error.stack屬性大量使用了JSStackFrame::GetMethodName(),事實(shí)上,我們的性能測(cè)試也表明,我們的改變使事情大大加快。

圖3

但回到Chrome DevTools的話題上,即使沒(méi)有使用error.stack,方法名稱也被計(jì)算出來(lái)了,這看起來(lái)不對(duì)。

這里有一些歷史可以幫助我們:

傳統(tǒng)上,V8有兩種不同的機(jī)制來(lái)收集和表示上述兩種不同的API(C++ v8::StackFrame API和JavaScript stack trace API)的堆棧跟蹤。有兩種不同的方式來(lái)做(大致)相同的事情是容易出錯(cuò)的,并且經(jīng)常導(dǎo)致不一致和錯(cuò)誤,所以在2018年底,我們啟動(dòng)了一個(gè)項(xiàng)目,以解決堆棧跟蹤捕獲的單一瓶頸。

這個(gè)項(xiàng)目非常成功,大大減少了與堆棧跟蹤收集有關(guān)的問(wèn)題的數(shù)量。大部分通過(guò)非標(biāo)準(zhǔn)的error.stack屬性提供的信息也是懶散地計(jì)算的,只有在真正需要的時(shí)候才會(huì)計(jì)算,但作為重構(gòu)的一部分,我們對(duì)v8::StackFrame對(duì)象采用了同樣的技巧。

所有關(guān)于堆??蚣艿男畔⒍际窃谌魏畏椒ǖ谝淮伪徽{(diào)用時(shí)計(jì)算的。

這通常會(huì)提高性能,但不幸的是,它被證明與Chromium和DevTools中使用這些C++ API對(duì)象的方式有些相反。

特別是由于我們引入了一個(gè)新的v8::internal::StackFrameInfo類,它持有關(guān)于堆??蚣艿乃行畔?,這些信息通過(guò)v8::StackFrame或通過(guò)error.stack公開,我們總是計(jì)算兩個(gè)API提供的信息的超集,這意味著對(duì)于v8::StackFrame的使用(特別是對(duì)于DevTools),我們也將計(jì)算方法名稱,只要關(guān)于堆??蚣艿娜魏涡畔⒈徽?qǐng)求。事實(shí)證明,DevTools總是立即請(qǐng)求源和腳本信息。

基于這一認(rèn)識(shí),我們能夠重構(gòu)并大幅簡(jiǎn)化堆??蚣艿谋硎荆⑹蛊涓討卸?,因此整個(gè)V8和Chromium的使用現(xiàn)在只需要支付計(jì)算他們所要求的信息的成本。這給DevTools和其他Chromium用例帶來(lái)了巨大的性能提升,它們只需要關(guān)于堆??蚣艿囊恍〔糠中畔?基本上只是腳本名稱和以行和列偏移形式存在的源位置),并為更多的性能改進(jìn)打開了大門。

函數(shù)名稱

隨著上述重構(gòu)的完成,符號(hào)化的開銷(在v8_inspector::V8Debugger::symbolize中花費(fèi)的時(shí)間)被減少到整個(gè)執(zhí)行時(shí)間的15%左右,而且我們可以更清楚地看到V8在(收集和)符號(hào)化堆棧幀以便在DevTools中使用時(shí)的時(shí)間。

[[399058]]

圖4

第一件引人注目的事情是計(jì)算行數(shù)和列數(shù)的累積成本。

這里昂貴的部分實(shí)際上是計(jì)算腳本中的字符偏移量(基于我們從V8得到的字節(jié)碼偏移量),結(jié)果發(fā)現(xiàn),由于我們上面的重構(gòu),我們做了兩次,一次是在計(jì)算行號(hào)時(shí),另一次是在計(jì)算列號(hào)時(shí)。

在v8::internal::StackFrameInfo實(shí)例上緩存源位置有助于快速解決這個(gè)問(wèn)題,并且完全消除了v8::internal::StackFrameInfo::GetColumnNumber的任何配置文件。

對(duì)我們來(lái)說(shuō),更有趣的發(fā)現(xiàn)是,v8::StackFrame::GetFunctionName在我們看的所有配置文件中都出奇地高。深入研究后我們發(fā)現(xiàn),計(jì)算我們?cè)贒evTools中為堆??蚣苤械暮瘮?shù)顯示的名稱是不必要的,成本很高。

  • 首先尋找非標(biāo)準(zhǔn)的 "displayName "屬性,如果它產(chǎn)生了一個(gè)具有字符串值的數(shù)據(jù)屬性,我們就會(huì)使用它。
  • 否則就返回到尋找標(biāo)準(zhǔn)的 "name "屬性,并再次檢查該屬性是否產(chǎn)生了一個(gè)值為字符串的數(shù)據(jù)屬性。
  • 并最終返回到由V8解析器推斷出的、存儲(chǔ)在函數(shù)字面上的內(nèi)部調(diào)試名稱。

"displayName "屬性是為了解決函數(shù)實(shí)例上的 "name "屬性在JavaScript中只讀且不可配置的問(wèn)題而添加的,但它從未被標(biāo)準(zhǔn)化,也沒(méi)有被廣泛使用,因?yàn)闉g覽器的開發(fā)工具添加了函數(shù)名稱推理,在99.9%的情況下都能完成工作。

除此之外,ES2015讓Function實(shí)例上的 "name "屬性變得可配置,完全消除了對(duì)特殊 "displayName "屬性的需求。

由于 "displayName "的負(fù)向查找成本很高,而且并不是真的需要(ES2015是在五年前發(fā)布的),我們決定從V8(和DevTools)中刪除對(duì)非標(biāo)準(zhǔn)的fn.displayName屬性的支持。

隨著 "displayName "的負(fù)向查找的完成,v8::StackFrame::GetFunctionName的一半成本被移除。另一半則用于通用的 "name "屬性查詢。幸運(yùn)的是,我們已經(jīng)有了一些邏輯來(lái)避免在(未觸及的)Function實(shí)例上進(jìn)行昂貴的 "name "屬性查詢,我們?cè)诓痪们暗腣8中引入了這一邏輯,以使Function.prototype.bind()本身更快。我們移植了必要的檢查,允許我們首先跳過(guò)昂貴的通用查詢,結(jié)果是v8::StackFrame::GetFunctionName不再出現(xiàn)在我們考慮的任何配置文件中。

總結(jié)

通過(guò)上述改進(jìn),我們已經(jīng)大大減少了DevTools在堆棧跟蹤方面的開銷。

視頻

我們知道仍有各種可能的改進(jìn)。

例如,使用MutationObservers時(shí)的開銷仍然很明顯,正如chromium:1077657所報(bào)告的那樣,但就目前而言,我們已經(jīng)解決了主要的痛點(diǎn),而且我們將來(lái)可能會(huì)回來(lái)進(jìn)一步簡(jiǎn)化調(diào)試性能。

 

chromium:1077657: https://bugs.chromium.org/p/chromium/issues/detail?id=1077657

 

責(zé)任編輯:武曉燕 來(lái)源: TianTianUp
相關(guān)推薦

2017-10-20 10:09:01

代碼CocoaPods編譯

2017-05-11 11:30:43

MySQL查詢速度

2020-02-28 09:26:54

PythonGo語(yǔ)言C語(yǔ)言

2022-04-27 09:24:22

前端代碼速度

2017-05-10 16:09:12

MySQL數(shù)據(jù)庫(kù)查詢

2020-02-14 09:40:14

人工智能機(jī)器學(xué)習(xí)技術(shù)

2011-05-30 13:28:00

PHP

2009-03-30 14:12:38

LinuxUnladenSwallow

2018-07-30 15:05:26

Hadoop大數(shù)據(jù)集群

2021-07-21 17:03:35

Chrome網(wǎng)絡(luò)釣魚瀏覽器

2016-09-07 15:02:03

ElasticSear索引速度

2024-06-27 11:00:07

2023-05-04 07:34:37

Rust代碼CPU

2017-05-25 15:14:36

2020-08-21 10:59:10

微軟服務(wù)器運(yùn)維

2020-06-03 11:26:05

算法移動(dòng)設(shè)技術(shù)

2020-03-13 19:00:38

Windows 10Windows開機(jī)速度

2011-05-19 11:33:38

數(shù)據(jù)庫(kù)訪問(wèn)速度

2019-09-24 09:25:05

Vue項(xiàng)目加載

2021-01-28 08:00:00

Windows 10Windows微軟
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)