<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          使用 Performance 看看瀏覽器在做什么

          共 4353字,需瀏覽 9分鐘

           ·

          2022-06-29 20:37

          前言

          Chrome 瀏覽器的 Performance 面板為我們提供了檢測(cè)頁(yè)面性能的能力,但其提供的遠(yuǎn)不止一些性能數(shù)據(jù)。本文將從工作原理的視角,結(jié)合實(shí)際工程的錄制結(jié)果,探一探性能面板向我們透露的其他信息。

          性能面板

          關(guān)于面板的功能與使用方法,可以參考這篇文章[1]。本節(jié)主要介紹瀏覽器架構(gòu)與性能面板的關(guān)系。

          因?yàn)樯形礇Q出最終的標(biāo)準(zhǔn)架構(gòu),各大瀏覽器的實(shí)現(xiàn)細(xì)節(jié)各有不同。這里我們以 Chrome 的架構(gòu)為例,對(duì)照其架構(gòu)與性能面板的關(guān)系。由下圖我們可以看到性能面板呈現(xiàn)的幾個(gè)主要線程。性能面板并不包含架構(gòu)中全部的線程,主要還是與頁(yè)面渲染過(guò)程相關(guān)的部分。

          Network

          Network 代表瀏覽器進(jìn)程中的網(wǎng)絡(luò)線程,我們可以看到時(shí)間軸上包含了所有的網(wǎng)絡(luò)請(qǐng)求和文件下載的調(diào)用信息,并以不同顏色標(biāo)識(shí)不同類型的資源。

          Main

          Main 代表渲染進(jìn)程中的主線程,渲染相關(guān)的事情基本都是它來(lái)做,腳本執(zhí)行、樣式計(jì)算、布局計(jì)算、繪制等等。

          Compositor & Raster

          Compositor 代表渲染進(jìn)程中的合成線程,Raster 代表渲染進(jìn)程中的柵格線程。如今瀏覽器繪制一個(gè)頁(yè)面,可以分為以下幾步:

          • 主線程將頁(yè)面分成若干圖層(后文中會(huì)提及 Update Layer Tree)
          • 柵格線程分別對(duì)每一個(gè)層進(jìn)行柵格化處理
          • 合成線程將柵格化的圖塊合并成一個(gè)頁(yè)面

          我們可以看到,在性能面板中主線程在最后調(diào)用了柵格線程做實(shí)際的渲染。

          GPU

          顯然,這部分就是 GPU Process 中的 GPU 線程。

          瀏覽器的工作報(bào)告

          接下來(lái)我們將大致從時(shí)間維度,看看瀏覽器錄制下來(lái)的「工作報(bào)告」。

          文檔的下載解析

          我們旅途的起點(diǎn)將從點(diǎn)擊 Chrome Performance Panel 的 Reload[2] 按鈕(形如刷新)開(kāi)始。當(dāng)前頁(yè)面首先進(jìn)行卸載,伴隨著幾個(gè)日志上報(bào),瀏覽器開(kāi)始了 index.html 的下載工作。HTML 文檔下載完成后,瀏覽器開(kāi)始按照 HTML 標(biāo)準(zhǔn)對(duì) index.html 進(jìn)行解析[3],在主線程中將接收到的文本字符串解析為 DOM 。我們可以注意到,HTML 的解析過(guò)程并不是一氣呵成,這是因?yàn)?HTML 通常還包括了其他外部資源,如圖片、CSS、JS 等。這些文件需要通過(guò)網(wǎng)絡(luò)請(qǐng)求或緩存來(lái)獲取。其中,當(dāng) HTML 解析器解析到 <script> 標(biāo)簽時(shí),HTML 文檔的解析過(guò)程就會(huì)中止,轉(zhuǎn)而去加載、解析和執(zhí)行腳本。因此,從主線程的時(shí)間軸可以看出,Parse HTML 的過(guò)程是斷斷續(xù)續(xù)的。

          不同資源的處理

          以下處理策略都可以在主線程中看到,但是不同資源的處理?xiàng)l長(zhǎng)短差距較大,截圖困難,這里不做呈現(xiàn)。

          那么瀏覽器對(duì)不同資源的處理策略是怎樣的呢?

          • 瀏覽器下載 HTML 并解析,如果遇到外部 CSS 等資源,就會(huì)由 Browser 進(jìn)程中的 network 線程下載
          • 當(dāng) CSS 下載時(shí),HTML 的解析過(guò)程可以繼續(xù)
          • 當(dāng)解析遇到了外部 Script 標(biāo)簽(不包含 async、defer 屬性)時(shí),解析停止,直到腳本下載并執(zhí)行完成

          總的來(lái)說(shuō),瀏覽器對(duì) HTML 的解析過(guò)程不會(huì)被 CSS、IMG 等資源的下載阻塞,但腳本的加載和執(zhí)行會(huì)終止 HTML 的解析。這主要是因?yàn)?JS 可能會(huì)改變 DOM 的結(jié)構(gòu),或者是 JS 動(dòng)態(tài)加載其他 JS 再改變 DOM 等潛在問(wèn)題。

          顯然,盡管瀏覽器可以并發(fā)幾個(gè) network 線程下載資源,但如果僅像上述策略這樣處理,當(dāng)解析到 <script> 時(shí),如果文件較大或者延遲較高,可能會(huì)發(fā)生「腳本獨(dú)占線程而沒(méi)有其他資源在下載」的空窗期(idle network)。因此,pre-loader[4] (或者 preload scanner 等叫法)將會(huì)在主線程之外,掃描余下的標(biāo)簽,充分利用 network 線程下載其他資源。這種機(jī)制可以優(yōu)化 19\%[5] 的加載時(shí)長(zhǎng)。

          腳本的解析執(zhí)行

          對(duì)于重業(yè)務(wù)邏輯的復(fù)雜中后臺(tái)應(yīng)用而言,腳本帶來(lái)的性能開(kāi)銷,往往是占主要地位的。我們從下圖的例子就可以看出,去除 beforeunload 之前的卸載,腳本本身的時(shí)間開(kāi)銷占比已過(guò)半。解析 HTML 在其次,至于其他樣式計(jì)算、微任務(wù)、垃圾回收等等,倒不是最痛的地方。當(dāng)然,該例子工程本身重業(yè)務(wù)邏輯,JavaScript 代碼量決定著其高成本。有時(shí)我們可以考慮使用 async 或者 defer 屬性來(lái)提高頁(yè)面性能,二者的差異不再贅述。需要專門(mén)說(shuō)明的是動(dòng)態(tài)添加腳本的情況。如下面示例代碼所示,腳本被 append 到文檔中后就會(huì)開(kāi)始下載,并且默認(rèn)和 async 具有一樣的行為,即「先加載完的先執(zhí)行」。

          let script = document.createElement('script');
          script.src = "/xxx/a.js";
          document.body.append(script);
          復(fù)制代碼

          如果專門(mén)設(shè)置了 async 屬性,則會(huì)按照 defer 的行為來(lái),即「先加載到的先執(zhí)行」。

          function loadScript(src{
            let script = document.createElement('script');
            script.src = src;
            script.async = false;
            document.body.append(script);
          }

          // 因?yàn)?async = false,所以按順序先執(zhí)行 big;否則(一般會(huì)先)執(zhí)行 small
          loadScript("/xxx/big.js");
          loadScript("/xxx/small.js");
          復(fù)制代碼

          從下圖中可以看到,調(diào)用棧中執(zhí)行的 appendChild 方法動(dòng)態(tài)添加了 script 腳本,之后很快開(kāi)始了下載動(dòng)作。動(dòng)態(tài)加載的腳本完成下載后,又第一時(shí)間開(kāi)始了腳本執(zhí)行。

          lifecycle 和 paint timing

          下圖展示的是文章[6]中提及的頁(yè)面生命周期流程圖。本節(jié)我們結(jié)合 Performance,對(duì)照該圖進(jìn)行觀察。

          beforeunload

          因?yàn)?Performance 的錄制是在已有頁(yè)面上進(jìn)行 reload,所以記錄的生命周期從頁(yè)面的卸載開(kāi)始。如下圖 Main 所示,beforeunload 事件[7]首先被瀏覽器觸發(fā)??梢宰⒁獾剑S色條 Event: beforeunload 是瀏覽器自身觸發(fā)的活動(dòng),我們稱之為根活動(dòng)(Root activities)[8]。

          pagehide

          從下圖中我們可以注意到,為什么事件的觸發(fā)順序和上面的生命周期流程圖不一致,是 pagehide -> visibilitychange -> unload 呢?事實(shí)上,在瀏覽器之前的設(shè)計(jì)中,如果頁(yè)面在卸載階段可視,visibilitychange 就會(huì)在 pagehide 之后觸發(fā),正如下圖截圖中一樣。這就使得頁(yè)面的卸載在不同可視情況下,有著不一致的生命周期與事件順序,給開(kāi)發(fā)者帶來(lái)復(fù)雜性。在未來(lái)新版本瀏覽器中,卸載階段的事件順序會(huì)進(jìn)行統(tǒng)一,目前進(jìn)度在這一 issue[9] 下。也正因?yàn)檫@部分的調(diào)整,unload 已經(jīng)不建議在代碼實(shí)現(xiàn)中使用了。

          first paint

          首先區(qū)分下以下兩個(gè)時(shí)間點(diǎn):

          • first paint:指的是首個(gè)像素開(kāi)始繪制到屏幕上的時(shí)機(jī),例如一個(gè)頁(yè)面的背景色
          • first contentful paint:指的是開(kāi)始繪制內(nèi)容的時(shí)機(jī),如文字或圖片
          image.png

          從 Performance 中,我們可以看出首次繪制的一系列動(dòng)作(有些過(guò)程啪的一下很快啊,截圖就省了):

          1. CSS 加載完成
          2. Parse Stylesheet:解析樣式表,構(gòu)建出 CSSOM
          3. Recalculate Style:重新計(jì)算樣式,確定樣式規(guī)則
          4. Layout:根據(jù)計(jì)算結(jié)果進(jìn)行布局,確定元素的大小和位置
          5. Update Layer Tree:更新渲染層樹(shù)
          6. Paint:根據(jù) Layer Tree 繪制頁(yè)面(位置、大小、顏色、邊框、陰影等)
          7. Composite Layers:組合層,瀏覽器將圖層合并后輸出到屏幕

          Layout 之后的過(guò)程很快,這里放大些倍數(shù)來(lái)查看:

          DOMContentLoaded

          DOMContentLoaded 表示 HTML 已經(jīng)完全被加載和解析,當(dāng)然樣式表、圖片等資源還不一定已經(jīng)完成加載。從下圖中可以看到,經(jīng)過(guò)多段 HTML 解析后,DCL 之后就沒(méi)有其他的 Parse HTML 了。

          pageshow/load

          因?qū)Ш蕉沟脼g覽器在窗口內(nèi)呈現(xiàn)文檔時(shí),瀏覽器會(huì)在 window 上觸發(fā) pageshow 事件,具體的時(shí)機(jī)可參考這里[10]。不僅如此,當(dāng)頁(yè)面是初次加載時(shí),pageshow 事件會(huì)在 load 事件后觸發(fā)。

          那么回到 Performance 的時(shí)間軸,從下圖我們可以看到,在紅色虛線(標(biāo)志著 load)之后,瀏覽器觸發(fā)了 pageshow 事件,也就是上文提及的根活動(dòng)。

          任務(wù)與性能問(wèn)題

          比較可惜的是,Performance 還無(wú)法清晰的看出 Event Loop。下圖中灰色的 Task 并不是指宏任務(wù),其代表的是「當(dāng)前主線程忙碌,無(wú)法響應(yīng)用戶交互」;Run Microtasks 則確實(shí)是在一次任務(wù)的末尾執(zhí)行的微任務(wù)。當(dāng)我們點(diǎn)開(kāi)調(diào)用棧觀察時(shí),可以看到源碼中的回調(diào)函數(shù)以及對(duì)應(yīng)的源碼位置。通過(guò) Task 可以定位性能出現(xiàn)問(wèn)題的地方。RAIL 模型[11]告訴我們需要重點(diǎn)關(guān)注占用 CPU 超出 50ms 的復(fù)雜任務(wù),以提供連貫的交互體驗(yàn)。當(dāng)然,這里更多的是對(duì)交互階段的響應(yīng)的要求,而不一定是對(duì)初始加載階段的要求。

          總結(jié)

          本文從工作原理的視角,結(jié)合實(shí)際工程的錄制結(jié)果,進(jìn)行了一次實(shí)踐對(duì)理論知識(shí)的檢驗(yàn)。Performance 不僅是性能分析工具,還是探究瀏覽器工作原理的小霸王學(xué)習(xí)機(jī)??偟膩?lái)說(shuō),瀏覽器的工作是充實(shí)且復(fù)雜的,與我們打工人的摸魚(yú)日常形成了對(duì)比,還是需要進(jìn)一步加深學(xué)習(xí)與思考呀。

          關(guān)于本文

          來(lái)自:ES2049

          https://juejin.cn/post/6904582930174705677



          瀏覽 47
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码婷婷 | 国产高清操逼视频 | 国产美女一级毛片真精品酒店 | 操逼视频精品 | 欧美性爱国产乱伦 |