搞定Chrome運行時的性能、內存問題 [太實用了]

作者:JamesZhang

https://juejin.im/post/5ec9c45b51882542e722e78d

初診

打開Chrome隱身模式

  • 主要是爲了確保有一個乾淨的測試環境, 不被其它因素所影響.

打開測試地址

  • 谷歌性能測試地址 https://googlechrome.github.io/devtools-samples/jank/

  • 國內性能測試地址:https://gitee.com/hellojameszhang/vue-example/tree/master/vue-javascript/public/performanceTest.html

  • 可以看到如下畫面:

可以看到頁面藍色小方塊在運動

限制CPU速度

  • 有些用戶電腦的CPU性能很好, 可能無法較好的分析問題(難以發現低端配置設備的性能問題), 所以需要降速.

  • 在Chrome瀏覽器控制檯中找到"Performance" => "CPU"選項, 選擇降低4/6倍性能

  • 添加更好小方塊, 找到性能瓶頸

  • 上面已經限制了CPU的性能, 接下來需要尋找性能瓶頸了.

  • 多次點擊"Add 10", 向頁面中添加小塊, 直到感覺頁面上小塊運動時出現明顯卡頓即可.

  • 此時, 已經可以明顯感覺到頁面很卡頓了.

優化前後的效果對比

  • 通過點擊"Optimize"按鈕, 可以提前感受下優化前後的效果對比

  • 可以明顯感受到優化前後頁面的流暢度明顯不一樣.

  • 瞭解Performance各模塊

  • 如何分析問題, 肯定是要依賴數據, 這裏需要使用到Chrome的Performance功能.

  • 將頁面切換至非優化的狀態, 點擊"Record".

  • 錄製的結果如下圖所示:

  • 以上數據初學者可能看不明白, 沒關係, 我們來一步步瞭解各部分的含義

  • FPS

    • fps, 指頁面每秒幀數

    • fps = 60性能最佳

    • fps < 24會讓用戶感覺到卡頓, 因爲人眼的識別主要是24幀

圖中藍色標記出的區域, 即FPS記錄的信息

放大某一區域, 可以看到, FPS由兩部分組成:

  • 紅色的條

  • 綠色的半透明條

  • 切換至優化狀態

  • 切換至已優化狀態, 再進行錄製後, 得到FPS數據如下:

  • 可以看出:

    • 沒有了紅色條

    • 綠色半透明條的高度, 明顯比沒有優化時高不少

小結

  • 紅色, 即幀數已經下降到影響用戶體驗的程度, Chrome已經標註出來, 這塊有問題

  • 綠色, 即FPS指數, 所有綠色柱體高度越高, 性能越好###瞭解CPU

  • 上圖中FPS下的位置, 即CPU信息

  • 我們採集些一個真實業務的CPU數據, 這裏我抓取的是個人簡書的performance, 如下圖所示:

  • 對比可以發現, CPU數據的一些特性:

  • CPU包括兩種狀態

    • 充滿顏色

    • 不充滿顏色

CPU是否充滿顏色和FPS存在聯繫

  • 瞭解NET

  • NET部分可以將屏幕逐幀錄製下來, 能幫忙觀察頁面的狀態, 分析首屏渲染速度

  • 瞭解Frames

  • 查看特定幀的FPS

  • Frames部分, 主要用於查看特定幀的FPS, 可以查看特定的幀情況, 懸停其上, 可以查看數據.

可以看到:

  • 這一幀的時間間隔是: 327ms

  • 當前FPS是1000ms/327ms = 3fps

  • 這裏主要體現的是頁面兩次刷新之間間隔了327ms

  • 查看某個Frames塊更詳細的信息

  • 點擊某個Frames塊, 可以查看到更加詳細的數據

  • Duration, 是當前幀從1.02s開始等待, 1.02s+326.97ms後進行了一次渲染fps, 1000ms/326.97ms = 3fps

  • 最下面的是當前幀的視圖畫像

瞭解FPS快捷工具

  • 在Chrome中, 還有一個More Tools選項, 選中"Rendering"選項

  • 接着, 開啓"FPS meter"選項

  • 勾選後, 在頁面上會出現一個FPS統計器, 如上圖左上角.

  • 暫時先不勾選"FPS meter", 不利於系統性學習

找到瓶頸

  • 通過前面的內容, 我們已經知道頁面有性能問題, 那接下來就要開始尋找原因了.

  • 瞭解 Summary

  • 對性能進行錄製完成時, 會默認在底部顯示一個 Summary摘要, 顯示全局信息.

  • 上面展示了0~4.90s錄製時間的具體耗時:

    • script, 耗時1534ms

    • rendering, 耗時2557ms

    • Painting, 耗時281ms

  • 主要了解這3個耗時, 但瞭解這3個耗時, 對於哪有問題, 還需要進一步的排查.

瞭解 Main

  • 上圖紅色框出部分, 就是Main, 其中每一塊是每一幀中所做的事情

  • 目前仍看不出什麼. 爲了方便觀看, 我們可在fps, cpu, net模塊, 點擊一下, 縮小時間區間:

  • 如上圖所示, 通過縮小時間區間, 來實現放大Main中的內容. 現在已經能夠看到, Main中展示的"火焰圖", 即函數調用的堆棧. 其中:

  • x軸表示時間

  • y軸表示調用函數, 函數中還包含依次調用的函數, y軸只佔用x軸的一個時間維度

  • 識別問題, 紅色三角號

  • 上圖中, 可以看到Animation Frame Fired右上角有一個紅色三角號, 這是Chrome自動幫助識別出有問題的部分, 就像FPS中的紅色一樣, 用來識別問題

  • 上圖可以到提示"Warning: Recurring handler took 318.21 ms", 即重複處理程序耗時318.21ms

追溯問題, 定位代碼問題

  • 點擊Animation Frame Fired下面的"Function Call"

  • 如上圖, 可以看到函數調用代碼中的位置, 可以點擊進行查看:

  • 雖然定位到了, 是方法 update造成的問題, 但不夠明確, 所以需要進一步探索

進一步分析問題位置

  • 繼續查看Main, 可以看到app.update下面有很多"紫色"的條, 紫色條本身表示渲染. 但請注意!!!的是紫色條上還有更小的, 運用前面學過的放大功能, 調整時間區間.

  • 可以看到, 每個紫色條都有一個紅色的小三角, 前面提到"紅色三角是Chrome幫助自動識別有問題的地方", 查看提示信息: "Forced reflow is a likely performance bottleneck.", 即強制迴流可能是性能瓶頸

  • 點擊查看摘要.

  • 可以看到, 問題定位在了 performanceTest.html的第136行, 點擊查看, 能夠看到是對每一個元素進行樣式修改.

  • 這段代碼的問題在於, 在每個動畫幀中, 它會更改每個方塊的樣式, 然後查詢頁面上每個方塊的位置. 由於樣式發生了變化, 瀏覽器不知道每個方塊的位置是否發生了變化, 因此必須重新佈局方塊以計算其位置.

  • 避免這種情況的出現, 可以參考: 避免大型、複雜的佈局和佈局抖動

對比優化的效果

  • 可以看到, 優化後的狀態, script, render的時間都大大減少了, 因此fps明顯提高.性能優化知識儲備

  • 使用 rail 模型測量性能

前端巔峯公衆號小編補充

由於鏈接是gw的,這邊有的人可能訪問不了

優化思路

避免強制同步佈局

  • 首先 JavaScript 運行,然後計算樣式,然後佈局。但是,可以使用 JavaScript 強制瀏覽器提前執行佈局。這被稱爲強制同步佈局。

  • 要記住的第一件事是,在 JavaScript 運行時,來自上一幀的所有舊佈局值是已知的,並且可供您查詢。因此,如果(例如)您要在幀的開頭寫出一個元素(讓我們稱其爲“框”)的高度,可能編寫一些如下代碼:

requestAnimationFrame(logBoxHeight);

function logBoxHeight() {
  // Gets the height of the box in pixels and logs it out.
  console.log(box.offsetHeight);
}
  • 如果在請求此框的高度之前,已更改其樣式,就會出現問題:

function logBoxHeight() {

  box.classList.add('super-big');

  // Gets the height of the box in pixels
  // and logs it out.
  console.log(box.offsetHeight);
}

避免佈局抖動

  • 有一種方式會使強制同步佈局甚至更糟:接二連三地執行大量這種佈局。看看這個代碼:

function resizeAllParagraphsToMatchBlockWidth() {

  // Puts the browser into a read-write-read-write cycle.
  for (var i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + 'px';
  }
}
  • 此代碼循環處理一組段落,並設置每個段落的寬度以匹配一個稱爲“box”的元素的寬度。這看起來沒有害處,但問題是循環的每次迭代讀取一個樣式值 (box.offsetWidth),然後立即使用此值來更新段落的寬度 (paragraphs[i].style.width)。在循環的下次迭代時,瀏覽器必須考慮樣式已更改這一事實,因爲 offsetWidth 是上次請求的(在上一次迭代中),因此它必須應用樣式更改,然後運行佈局。每次迭代都將出現此問題!

  • 此示例的修正方法還是先讀取值,然後寫入值:

// Read.
var width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = width + 'px';
  }
}

特別提示

  • 這篇文章核心點有兩個

    • 怎麼運行時調試瀏覽器

    • 告訴你爲什麼這次會出現性能問題

這次出現性能問題的關鍵點:讀取了dom的offsetWidth、offsetTop等屬性,會造成瀏覽器強制更新渲染隊列並且重排-重繪

推薦閱讀:

掌握了這些css屬性,再也不要加班打代碼了!

    相見恨晚的git命令演示動畫,一看就懂。

    圖片壓縮不求人,一個親測實用的圖片壓縮神器。

    12 個實用的前端開發技巧總結。


以上就是今日份的推薦,要是你也喜歡的話,就動動小手指,點個「在看」吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章