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

          騰訊文檔:大型前端項目內(nèi)存優(yōu)化總結(jié)

          共 5254字,需瀏覽 11分鐘

           ·

          2021-04-25 11:36

             戳藍字「TianTianUp」關(guān)注我們哦!


          在騰訊文檔表格中,如果用戶打開表格的內(nèi)容非常多,比如有幾萬行或者幾十萬個單元格。內(nèi)存占用會居高不下,在使用的過程中非常容易出現(xiàn)崩潰,卡頓等問題,在進行一系列的優(yōu)化之后,寫下這篇文章,總結(jié)下期間用到的一些優(yōu)化方案,希望可以或多或少幫助或者啟發(fā)一下他人。

          怎么找出內(nèi)存問題

          在內(nèi)存這里,我們一般是借助 Google 瀏覽器的開發(fā)者工具,然后獲取下頁面的內(nèi)存快照,然后分享具體占用過大內(nèi)存的對象。

          除了查看內(nèi)存占用過大的對象之外,也可以切換到 Containment 這里查看一些占用內(nèi)存過大的全局對象。 

          常見的內(nèi)存優(yōu)化方案

          按需加載

          按需加載可能是能夠想到的最簡單的內(nèi)存優(yōu)化的方式,比如當某一個功能沒有使用的時候,不加載這個功能模塊,也不進行初始化。舉個例子,比如帳號管理模塊,在沒有點擊右上角頭像部分的時候,是不需要加載的,這個時候就可以改成按需加載。 

          在騰訊文檔表格的優(yōu)化中,部分改造為按需加載的模塊使用率:

          1. Account 組件改成按需加載 用戶使用率:0.8%

          2. QuickFill 組件改成按需加載 用戶使用率:1.2%

          優(yōu)化了這些模塊之后,內(nèi)存占用大概降低了 10M 左右,優(yōu)化效果并不明顯。老板說: 

          對象池

          對象池模式在初始化的時候會創(chuàng)建固定數(shù)量的緩存,并且每個對象都可以復用,對象沒有更新的需求。 

          對象池這種模式主要用于游戲或者數(shù)據(jù)庫連接池等場景。因為騰訊文檔表格優(yōu)化的時候沒有使用到對象池的方式,這里就不詳細描述了,有需要的可以自行 Google。

          享元

          享元模式的特點:

          • 屬性全部只讀或者私有化

          • 提供創(chuàng)建對象實例的工廠方法

          • 修改時重新創(chuàng)建對象實例

          • 使用 key 緩存對象,key 相同直接返回緩存對象實例

          舉個例子:假設(shè)目前有三個變量 ABC,引用的都是同一個內(nèi)存中的對象。 

          假設(shè)這個時候需要修改對象 A 的屬性,則需要使用新的屬性參數(shù),創(chuàng)建一個新的對象,講 A 指向新創(chuàng)建的對象,此時內(nèi)存當中的對象應該是這樣的: 

           這個時候,內(nèi)存當中就有兩個對象了,BC 還是指向的之前的對象,但是這個時候的 A 已經(jīng)指向新創(chuàng)建的對象了。

          下圖是在騰訊文檔表格中,部分使用享元模式優(yōu)化之后的內(nèi)存對比,可以發(fā)現(xiàn)優(yōu)化效果還是比較明顯的。 

          享元遇到的問題

          以為用享元就能解決問題了? 

          直到看到這個圖: 

          用戶在操作設(shè)置數(shù)據(jù)格式之后,前端提交到數(shù)據(jù)后臺,落盤失敗!

          在做享元優(yōu)化的時候,遇到一個特殊的問題,這里也記錄下,方便大家后面使用享元的時候注意。我們有一個對象,用來描述單元格的值,以及值的類型。如下所示: 

          type 表示類型,value 就是存儲的值。

          type 的類型有如下幾種 

           這個對象的 key=type+,+value

           假設(shè)此時實用如下參數(shù)創(chuàng)建一個對象:

          {  type: 1,  value: 123,}

          此時生成的 key='1,123'

          很明顯,這個時候的 value 和 type 的類型對應不上,但是由于我們沒有任何校驗,導致錯誤的創(chuàng)建了一個對象,并且以 key='1,123' 被緩存起來了。

          當下次創(chuàng)建的一個對象的時候,傳入如下的參數(shù):

          {  type: 1,  value: '123',}

          這里的參數(shù),跟上面的區(qū)別是 value 和 type 是對應上的,但是注意,這個時候生存的 key='1,123'。嗯?發(fā)現(xiàn)這個 key 在之前已經(jīng)創(chuàng)建過了,并且緩存了一個對象。這個時候,不會再去創(chuàng)建一個新的對象,而是將上面的對象直接返回給到使用方,然后報錯了。

          這個情況在騰訊文檔表格中就出現(xiàn)過,有的業(yè)務模塊創(chuàng)建了臨時的 ExtenedValue 對象,在傳參數(shù)的時候,類型和值對應不上,導致數(shù)據(jù)層在提交數(shù)據(jù)的時候報錯。

          那如何解決這個問題呢? 在創(chuàng)建 key 的時候,獲取到 value 的真實類型,將真實類型作為 key 的一部分,這樣,上述的兩個參數(shù)創(chuàng)建的就會是兩個不同的對象了。

          進階版內(nèi)存優(yōu)化方案

          對象存儲優(yōu)化

          一個 demo

           上圖中的代碼,占用內(nèi)存 4.8M。 

           上圖中的代碼,占用內(nèi)存 3.6M。

           對比兩者的內(nèi)存快照發(fā)現(xiàn)相差的就是 1.2M。這是為什么呢?

          對象在 v8 中的存儲方式

           首先來看下對象在 v8 中的存儲方式,在 v8 中,js 的對象使用 JSObject 對象來描述。這個對象有下面幾個主要屬性:

          • HiddenClass 用來存儲對象屬性存儲的位置信息

          • properties 用來存儲具名屬性

          • elements 用來存儲有順序的屬性,類似數(shù)組 在這種情況下,存儲在 properties 中的屬性,都是快屬性模式(使用數(shù)組的形式存儲)。

          那針對上面的問題,猜測可能是因為

          this.a = null

          初始化屬性 a 的時候,HiddenClass 的結(jié)構(gòu)會變化,從而增加內(nèi)存。接著使用 v8 的調(diào)試工具 d8 來驗證下: 

           從上面的 debug 信息中可以看出,初始化的時候,如果賦值為 null,會在 HiddenClass 的 DescriptorArray 中添加記錄,用來描述屬性 a 的初始值。 所以,在初始化屬性的時候,如果參數(shù)沒有傳遞該屬性的值,那就不要初始化為 null,因為這樣會占用內(nèi)存。

          優(yōu)化驗證

          針對上述的情況,我們優(yōu)化了騰訊文檔表格的單元格對象 CellData 的初始化過程。 

           可以發(fā)現(xiàn),優(yōu)化的效果非常明顯。30w 單元格的表格,CellData 的內(nèi)存占用從 24M 減少到了 12M。

          優(yōu)化到到這里的時候,老板再問我能達到預期目標的時候: 

          清除 delete 操作

          一個 demo

          有 delete 操作

           

          沒有 delete 操作

          從上面的圖中可以看出,沒有 delete 操作會比有 delete 操作的內(nèi)存占用少非常多。這又是為什么呢?

          v8 HiddenClass 的轉(zhuǎn)化過程

          v8 在初始化對象,給對象添加屬性的時候,會變更 hiddenClass 指針的指向,將其指向最新的節(jié)點。先看一個 v8 官方網(wǎng)站的圖: 

           在這個圖中,當我們給對象 o 添加屬性的時候,hiddenClass 的指針一直在變化,也就是說每次給對象添加新的屬性,都會生成一個新的 HiddenClass 節(jié)點,所有的 HiddenClass 節(jié)點組成了一個的鏈表(其實準確的說應該是 tree ),然后對象中的 hiddenClass 指針指向最新的節(jié)點。

          上面說的所有的 HiddenClass 節(jié)點其實組成了一棵樹,在 v8 里面叫做 transition tree。例如:假設(shè)這個時候,新創(chuàng)建一個對象,并且添加了一個新的屬性: 

           這樣看是不是就知道為什么是一棵樹了。

          那這些跟 delete 操作有什么關(guān)系呢?

          delete 操作導致 v8 的對象存儲模式退化為字典模式

          當對屬性進行 delete 操作的時候,會將上面所說的鏈表結(jié)構(gòu)打破,并且對象在 v8 內(nèi)的存儲方式也會發(fā)生變化。變成如下所示的結(jié)構(gòu): 

           或者直接看 v8 官方網(wǎng)站提供的圖: 

          可以看到,如果你對一個對象的屬性進行 delete 操作,就會導致對象的存儲方式退化到字典模式(慢屬性模式)。相對于之前的快屬性模式,這種存儲方式更加消耗內(nèi)存。所以這也是為什么 delete 操作會導致對象內(nèi)存占用增加的根本原因。

          驗證:

          const obj = {    a:1,    b: 2,}%DebugPrint(obj);delete obj.a;%DebugPrint(obj);

           刪除屬性 a 之后,退化到字典模式: 

          再看一下這個圖,我們發(fā)現(xiàn)在 HiddenClass 節(jié)點也有一個 back pointer ,指向上一個節(jié)點。 

          所以,如果你是按照對象添加屬性的反方向刪除屬性的話,對象并不會退化到慢屬性模式,或者對象的內(nèi)存占用并不會增加。這里更多詳細的描述可以參考 v8 的官方文檔或者 superzheng 大佬在知乎的專欄。

          如何替換掉 delete 操作?

          賦值給 undefined

          假設(shè)有如下代碼:

          const data = {  a: '1',  b: 'c',}// do something...delete data.c;

          如果沒有特殊的要求,建議直接修改為:

          const data = {  a: '1',  b: 'c',}// do something...data.c = undefined;

          使用如下代碼測試:

          const startTime = +new Date();  for (let i = 0; i < 1000000; i++) {    const a = {      a: '1',      b: '2',      c: '3',    };    // a.b = undefined;    delete a.b;  }  const endTime = +new Date();  console.log(endTime - startTime);

          賦值給 undefined 比直接 delete 刪除,要快幾十倍,甚至上百倍。

          使用 Map 替代

          假設(shè)你的對象必須就是要刪除,那么還可以使用 Map 來替代對象。

          const data = {  a: '1',  b: 'c',}// do something...delete data.c;

          可以修改為如下的形式:

          const data = new Map<string, string>();data.set('a', '1');data.set('b', 'c');// do something...// 使用 Map 對象提供的 delete 方法刪除元素data.delete('a');

          性能監(jiān)控(保護優(yōu)化成果)

          內(nèi)存監(jiān)控

          內(nèi)存優(yōu)化的手段固然重要,但是如果在優(yōu)化之后一段時間,隨著需求的增加,由于一些新代碼的引入,或者寫代碼時候不注意,就有可能導致內(nèi)存暴增。這個時候,就在想是否能夠在發(fā)布之前,就可以自動化的檢測到騰訊文檔的特征表格的內(nèi)存占用,然后跟現(xiàn)網(wǎng)環(huán)境的內(nèi)存占用對比。如果發(fā)現(xiàn)內(nèi)存占用比現(xiàn)網(wǎng)的要高很多,則可以在發(fā)布階段終止發(fā)布,接著進入代碼 review 階段,分析哪里導致的內(nèi)存占用增加。解決了問題之后,再重新進行發(fā)布流程。

          針對這個自動化測試,騰訊文檔這邊目前也實現(xiàn)了一個可以自動化獲取頁面內(nèi)存快照和大小的工具,可以接入到流水線當中,作為一個性能檢測的方式集成到發(fā)布流水線當中,保護內(nèi)存優(yōu)化成功。

          參考資料

          • https://flaviocopes.com/how-to-remove-object-property-javascript/
          • https://v8.dev/blog/fast-properties
          • https://zhuanlan.zhihu.com/p/28872382 https://v8.dev/docs/build


          最后,如果你對性能優(yōu)化感興趣,騰訊文檔歡迎你的加入。

          關(guān)于AlloyTeam


          AlloyTeam 是國內(nèi)影響力最大的前端團隊之一,核心成員來自前 WebQQ 前端團隊。
          AlloyTeam負責過WebQQ、QQ群、興趣部落、騰訊文檔等大型Web項目,積累了許多豐富寶貴的Web開發(fā)經(jīng)驗。

          這里技術(shù)氛圍好,領(lǐng)導nice、錢景好,無論你是身經(jīng)百戰(zhàn)的資深工程師,還是即將從學校步入社會的新人,只要你熱愛挑戰(zhàn),希望前端技術(shù)和我們飛速提高,這里將是最適合你的地方。

          加入我們,請將簡歷發(fā)送至 [email protected],或直接在公眾號留言~

          期待您的回復??

          最近文章:

          END



          如果覺得這篇文章還不錯
          點擊下面卡片關(guān)注我,交個朋友
          來個【分享、點贊、在看】三連支持一下吧

             “分享、點贊在看” 支持一波 

          瀏覽 160
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  一区二区三区四区久久久 | 麻豆成人视频 | 国产伦理,久久做,天天做 | 中文字幕22页 | 亚洲粉嫩18p |