<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>

          記一次平淡無奇的性能優(yōu)化

          共 3713字,需瀏覽 8分鐘

           ·

          2021-12-01 04:00

          背景

          公司項目最近用到甘特圖功能,于是集成了一款開源的甘特圖插件。

          甘特圖的主要作用是項目管理,可以用圖示的方式通過活動列表和時間刻度形象地表示出任何特定項目的活動順序與持續(xù)時間,如下圖

          image.png

          玩過甘特圖的同學(xué)都知道,甘特圖的前端實現(xiàn)基本靠繪畫。而繪畫是對前端的開發(fā)和性能要求非常高的一項技術(shù)。而頻繁的交互操作,也會導(dǎo)致開發(fā)的性能要求進一步嚴格。

          現(xiàn)象

          其基礎(chǔ)現(xiàn)象很簡單。當(dāng)我拖動甘特圖的視圖區(qū)域時,明顯感受到卡頓和拖影。各位同學(xué)都明白,涉及到繪畫相關(guān)的動畫操作,要60fps才能夠到順滑的階段。30fps勉強卡頓,20fps就卡頓拖影嚴重了。

          于是我采用了 Frame Rendering Stats 工具先進行肉眼觀察幀率數(shù)值。他的主要作用除了觀察當(dāng)前頁面操作的fps數(shù)值外,還可以監(jiān)控gpu的內(nèi)存用量。當(dāng)然這個工具的位置也很容易找。就在 Chrome Devtools 的 Rendering 選項中,勾選開啟即可

          image.png

          當(dāng)我使用工具進行 fps 的觀察,同時視圖區(qū)域進行穩(wěn)定勻速的滑動時,能夠感受到明顯的卡頓和拖影。其檢測數(shù)值最高僅有31fps,最低有26fps,卡頓的級別基本上屬于嚴重卡頓。如果換一臺低端一點的設(shè)備,那么其展示效果肯定無法想象。

          image.png

          分析

          既然我們發(fā)現(xiàn)了問題,那么就分析下問題到底出在哪里。接著打開 Performance 工具并開始錄制,錄制的同時對視圖區(qū)域進行穩(wěn)定勻速的滑動,滑動幾秒后停止錄制,拿到一份這樣的分析報告:

          image.png

          甘特圖插件和主要技術(shù)棧都是react。在 react 16最新的fiber架構(gòu)中,為了讓響應(yīng)可以更快的得到反饋,拆分了子任務(wù)。而根據(jù)一般的顯示器刷新率(60hz)和目前的瀏覽器所支持的最高刷新率來算,平均下來每一幀的任務(wù)時長一般只有16.6ms。當(dāng)單幀任務(wù)時長超過16.6ms時,就會產(chǎn)生卡頓和掉幀。

          但是根據(jù)分析情況來看,上圖滾動時產(chǎn)生的任務(wù)絕大多數(shù)都大于40ms,甚至還會產(chǎn)生longtask(Chrome官方對longtask的定義是大于50ms,即20fps)。所以接著展開來看,看看單任務(wù)中到底是哪些事件導(dǎo)致的執(zhí)行時間長。

          接著點開其中一個任務(wù),放大詳情??梢钥吹絪elftime(自身執(zhí)行時間)排名第一的是一個匿名函數(shù)。繼續(xù)點開右側(cè)的代碼堆棧,去看看哪行代碼執(zhí)行時間比較長。

          image.png

          點開后,會自動幫我們跳轉(zhuǎn)到 Devtools 中的 source 模塊,還會將代碼的執(zhí)行時間標(biāo)在函數(shù)的左側(cè)。從下文可以分析,第74行的?toLocaleDateString?的耗時非常嚴重。因為函數(shù)組件/類組件的渲染生成是同步的,所以耗時長會拖慢 render 的效率,進而拖慢整體的幀率。

          image.png

          時區(qū)轉(zhuǎn)換的鍋

          Date.prototype.toLocaleDateString()?的作用是對不同語言的時間文本進行轉(zhuǎn)換。例如

          const event = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };console.log(event.toLocaleDateString('de-DE', options));// expected output: Donnerstag, 20. Dezember 2012console.log(event.toLocaleDateString('ar-EG', options));// expected output: ??????? ?? ??????? ????console.log(event.toLocaleDateString(undefined, options));// expected output: Thursday, December 20, 2012 (varies according to default locale)

          但是這樣一個看起來人畜無害的方法,怎么會耗時這么長呢?

          鑒于直接翻看v8的這部分源碼比較硬核,我們選擇去查看 toLocaleDateString 的 polyfill —— formatjs。這個庫一直作為 Date 方面的國際化polyfill存在著,包括時區(qū)國際化和時間文本國際化。

          我們找到 formatjs 中的?packages/intl-datetimeformat/src/to_locale_string.ts?中的?toLocaleString?方法。這個方法創(chuàng)造了一個時間格式化對象:

          image.png

          繼續(xù)跟蹤?DateTimeFormat?類的實現(xiàn),可以看到有一個叫做?localeData?的變量。這個變量就是我們做國際化時的各國語言文本內(nèi)容。同樣上面有一個叫?tzData?變量,是時區(qū)數(shù)據(jù)庫的內(nèi)容:

          image.png

          接著一路跟蹤,會發(fā)現(xiàn)?ResolveLocale?方法是處理當(dāng)前選中時區(qū)的核心方法。在那里面,所有的國際化文本都會經(jīng)過運算篩選,再與當(dāng)前選中的語言文本進行匹配(尤其是下圖當(dāng)中這種 indexOf 高耗時方法)

          image.png

          解決方案

          對于此類調(diào)用耗時問題,唯一的解決就是對現(xiàn)有的執(zhí)行結(jié)果加緩存memo。方案很簡單:將時間轉(zhuǎn)化為時間戳作為緩存的key,存入緩存,后續(xù)直接從緩存讀取即可:

          image.png

          優(yōu)化后,我們再次用performance進行分析。發(fā)現(xiàn)不僅fps有肉眼和數(shù)值的顯著提升,且longtask也不再存在,平均任務(wù)耗時被壓縮到了23ms?;旧蠈崿F(xiàn)了流暢,解決了卡頓問題。

          image.png

          但,我們還要繼續(xù)解決?toLocaleDateString?的兄弟api:Intl.DateTimeFormat。

          關(guān)于 Intl.DateTimeFormat

          Intl.DateTimeFormat?是一個比較新的時間格式化api。他與?toLocaleDateString?在使用上最大的不同時,支持對任意的date對象進行format,api設(shè)計上偏向構(gòu)造器,更加利于緩存設(shè)計。例如用法:

          console.log(new Intl.DateTimeFormat('en-US').format(date));// expected output: "12/20/2020"console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long' }).format(date));// Expected output "Sunday, 20 December 2020 at 14:23:16 GMT+11"

          同樣,在對上面的?toLocaleDateString?進行性能優(yōu)化完畢后,排在react耗時后面的?Intl.DateTimeFormat?也值得處理。繼續(xù)查看代碼耗時:

          image.png
          image.png

          發(fā)現(xiàn)此方法的耗時也不低:7.1ms,有提升空間。而此方法的polyfill實現(xiàn),也和上面的?toLocaleDateString?一致,都是實例化?DateTimeFormat?對象才可以用。只不過區(qū)別是一個手動實例化,一個幫你實例化:

          image.png

          那我們就繼續(xù)對?Intl.DateTimeFormat?增加緩存。

          最終優(yōu)化結(jié)果

          按照對?toLocaleDateString?的優(yōu)化思路,我們只需要對?Intl.DateTimeFormat?實例進行優(yōu)化即可。依然是做緩存,只不過?key?換成了地區(qū) + 轉(zhuǎn)化選項這唯一的參數(shù):

          image.png

          優(yōu)化完畢后,我們再次采集一份performance樣本。

          通過檢測,fps已經(jīng)達到了最低45,最高50的數(shù)據(jù)。基本上實現(xiàn)流暢(因Devtools開啟狀態(tài)下也耗性能,實際使用幀率比這個高)。相比優(yōu)化前,提升了61%。long task消失不存在

          image.png

          結(jié)尾

          當(dāng)然,這份優(yōu)化歷程只是個初步優(yōu)化??梢钥吹?,雖然單個任務(wù)的耗時有所大幅度下降,但是還有提升空間存在。要盡量低于16.6ms才能夠?qū)崿F(xiàn)完全流暢。

          總結(jié)一下:盡量采用?Intl.DateTimeFormat?來替代?toLocaleDateString,并對構(gòu)造器進行緩存提升性能。在其他國際化的場景(例如數(shù)字等)也要注意這一點

          此外,這份性能優(yōu)化的方案已經(jīng)提交給了上游開源項目,并在8.15日已經(jīng)合并進倉庫:https://github.com/MaTeMaTuK/gantt-task-react/pull/19

          image
          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产系列第一页 | 欧美v亚洲v日韩v最新在线 | 五月丁香婷婷基地 | 黄 色 视 频小便一区二区三 | 8050网 午夜 |