看似簡單的input框輸入竟然異常卡頓,記一個日常性能問題的排查思路


壹 ❀ 引

我們公司產品主要提供企業項目管理服務,那么自然有配套的desk工單管理系統,用於搜集客戶bug以及相關問題反饋。有一天我在測試功能時碰巧發現了一個bug,所以就想着提一個工單記錄下方便日后修復。但就在創建工單填寫標題時我發現標題輸入卡爆了,本想着創建一個bug工單,結果又附帶新建了一個性能bug單....下圖便是當時問題表現:

可以看到我只要連續操作,輸入框都存在非常明顯的卡頓,輸入並不流暢,那么本文主要記錄此問題的排查思路,以及部分性能排查小技巧,本文開始。

貳 ❀ 排查過程

因為操作卡頓明顯,出於好奇,我打開了瀏覽器的Performance monitor工具,此工具能時時記錄瀏覽器CPU占用情況以及JS堆內存使用情況,具體入口見下圖:

於是我嘗試在標題欄連續輸入或者連續刪除,黃色區域非常明顯的像一座山峰一樣升了起來,如下圖可見CPU占用甚至離譜的飆升到了100%JS堆內存也從200M不到升到700多,我這還是M1芯片16g內存的mac.....確實性能堪憂。

經過簡單測試對比,我發現如果我慢慢的輸入,那么黃色區域就是一座座小山峰,會卡,但是不會卡那么久。但如果我連續輸入,那么黃色區域就直接形成一個梯形了,所以這里就可以斷定輸入框沒做防抖操作,如果有防抖,連續輸入表現應該和我有間隔的輸入表現一致。

上述分析也只是推斷出沒防抖,為什么這么卡?這就要需要通過Performance來具體分析了,打開控制台Performance,點擊⏺開始錄制,然后重復上述連續輸入的操作,關閉錄制,等待少許片刻,我們就能看到具體的火焰圖以及相關性能分析了,具體如下:

通過最下方的Summary可以看到整個過程持續4.45秒,而Script相關耗時就占用了3.19秒,說明主要是JS相關的邏輯在占用耗時。

將目光放到截圖的火焰圖區域,也就是黃色梯形部分,這里我一共連續操作了2次,所以形成了2端梯形,而在具體調用棧部分,我們能發現每段梯形正好對應一大段的Task;比如第一段操作我連續輸入了4個字符,所以下面生成了4個Task任務,第二段我連續輸入了5次,因此有5個Task任務,這也驗證了前面沒做防抖的推斷,每次輸入都需要執行大量javascript邏輯,那么具體是什么在耗時呢?

我們可以選中黃色梯形區域,盡量具體到一個Task任務范圍,然后拖動調用棧來看看一個Task任務到底在搞什么鬼:

當拖到調用棧底部,我們發現一個Task任務下面都對應了四次render(縱向從上到下是調用過程,橫向是耗時占用分布),搞半天每輸入一次都執行了4大段的render。注意,這里的四次render並不是表示一共只渲染了四次,考慮到時間切片的概念,chrome應該是把數百次render合成了一小段,其實這四段render加起來可能是由數百次render組合而成。

為了驗證這一點,我們打開React Developer Tools工具,選擇設置:

勾選General下的Highlight updates when components render.作用就是當組件重新render時都會標記高亮。

然后再次在標題輸入框輸入內容,隨后我人傻了....這個彈窗組件中數十個inputselect,會隨着每次標題輸入框值變化,都會重新渲染多次:

我從寫這塊邏輯的同學那了解到,這個組件在設計上確實存在問題,整個創建desk的彈窗在數據層其實都只依賴了一個this.state.form對象,來搜集整個彈窗中每個屬性的值,大概如下:

了解react的同學都知道,只要state發生變化,組件肯定得重新渲染,所以這個組件設計上就沒有做數據分塊,導致一個iuput變化引起整個組件都重新渲染的尷尬局面,那么到這里我們知道了卡頓的第二個原因,數據設計上沒做數據分塊,對於底層組件在是否需要渲染上也沒做判斷。

在后續溝通中我得知,這個組件導致大量渲染的問題是一直存在的,說明引發這次卡頓的根因還不是組件設計的問題(雖然確實有問題),那為何之前渲染這么多都不卡,怎么現在輸入一次就卡這么久,於是我繼續跟調用棧,點擊綠色區域,可以看到對應的文件:

首先文件引用來自ones-design這個三方庫,這個庫是我們公司自己基於antd封裝的一套組件庫,說明最終耗時都跟這個文件有關,點擊core.es.js就能看到具體代碼執行塊:

option.foEarch??難道跟select組件的options數量有關?在與組件庫的同學溝通了解,這塊邏輯還真是在對select選項做遍歷操作。而很不巧的是,因為我們自己項目desk工單創建上有一個叫客戶信息select,這個下拉框包含了數以萬計的下拉選項,options選項數據非常龐大....

而之前不卡是因為我同事在下拉框這里使用了虛擬列表的select,不管你多少選項,我固定只渲染十幾條,所以面對數萬條的數據,也能流暢展示。

那么到這里也驗證了我最初排查的一個猜想,因為剛發現這個問題時候,我就想着能不能在我本地開發環境復現,於是我也在新建工單設置那配置了數十個屬性,但是標題輸入依舊非常流暢,當時我就猜想是不是跟select相關組件的options有關。而這個性能問題看樣子也持續了一段時間了,為什么沒有客戶反饋這個問題呢?其實也是因為客戶側很難像我們這樣有這么龐大選項的select

於是我又跟蹤了一下這個組件最近的修改記錄,最后發現其實就是前兩周我一個同事,為了解決select樣式問題,將之前就有的虛擬列表select(之前那個select有樣式缺陷)替換成了我們自己最新的組件庫的select,從而導致了這個問題。

叄 ❀ 解決方案

那么到這里,我們統計一下排查下來發現的問題:

  • input輸入未做防抖,修改成本較少
  • 創建工單組件設計不合理,需要做數據拆分,修改成本大,需要重構
  • 在工單組件下層組件添加是否需要更新的判斷,比如將React.Component改為React.PureComponent,起碼能優化一點
  • 組件庫優化select,對於options操作做緩存,不要每次渲染都重復遍歷

那么最終修改方案采用了第三條與第四條,當時引發性能問題的同學,也只是修改了少量組件的創建方式為React.PureComponent,就發現創建上流暢了不少,組件庫那邊也緊急上了一個hotfix,對options操作了做了緩存,遍歷一次后之后的重復渲染都不引起二次遍歷。

那么下圖就是當時修改完成的效果了,可以看到我的輸入刪除都非常流暢了,雖然在組件創建階段這個select其實還是會卡頓一會,但是用戶已經很難感知到。

那么到這里本文結束!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM