<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)存性能提升的項目實踐

          共 2761字,需瀏覽 6分鐘

           ·

          2022-06-21 01:09

          現(xiàn)代的開發(fā)語言除了C++以外,大部分都對內(nèi)存管理做好了封裝,一般的開發(fā)者根本都接觸不到內(nèi)存的底層操作。更何況現(xiàn)在各種優(yōu)秀的開源組件應(yīng)用越來越多,例如mysql、redis等,這些甚至都不需要大家動手開發(fā),直接拿來用就好了。所以有些同學(xué)也會覺得作為應(yīng)用層開發(fā)的同學(xué)沒有學(xué)習(xí)的必要去學(xué)習(xí)底層。

          但我想通過本文的實際案例告訴大家,哪怕不直接接觸內(nèi)存底層操作,就只是用一些開源的工具,如果你能理解底層的工作原理,你也能夠用到極致。
          1
          用于訪問歷史存儲需求
          假如現(xiàn)在有這樣一個業(yè)務(wù)需求,用戶每次刷新都需要獲得要消費的新數(shù)據(jù),但是不能和之前訪問過的歷史重復(fù)。你可以把它和你經(jīng)常在用的今日頭條之類的信息流app聯(lián)系起來。每次都要看到新的新聞,但是你肯定不想看到過去已經(jīng)看過的文章。這樣在功能實現(xiàn)的時候,就必要保存用戶的訪問歷史。當用戶再來刷新的時候,首先得獲取用戶的歷史記錄,要保證推給用戶的數(shù)據(jù)和之前的不重復(fù)。當推薦完成的時候,也需要把這次新推薦過的數(shù)據(jù)id記錄到歷史里。

          為了適當降低實現(xiàn)復(fù)雜度,我們可以規(guī)定每個用戶只要不和過去的一萬條記錄重復(fù)就可以了。這樣每個用戶最多只需要保存一萬條歷史id,如果存滿了就把最早的歷史記錄擠掉。我們進一步具體化一下這個需求的幾個關(guān)鍵點:

          • 每個數(shù)據(jù)id是一個int整數(shù)來表示
          • 每個用戶要保存1萬條id

          • 每次用戶刷新開始的時候需要將這1萬條歷史全部讀取出來過濾一遍

          • 每次用戶刷新結(jié)束的時候需要將新訪問過的10條寫入一遍,如果超過1萬需將最早的記錄擠掉

          可見,每次用戶訪問的時候,會涉及到一個1萬規(guī)模的數(shù)據(jù)集上的一次讀取和一次寫入操作。
          好了,需求描述完了,我們怎么樣進行我們的技術(shù)方案的設(shè)計呢?相信你也能想到很多實現(xiàn)方案,我們今天來對比兩個基于Redis下的存儲方案在性能方面的優(yōu)劣。
          2
          Redis方案一:用list存儲 

          首先能想到的第一個辦法就是用Redis的List來保存。因為這個數(shù)據(jù)結(jié)構(gòu)設(shè)計的太適合上面的場景了。List下的lrange命令可以實現(xiàn)一次性讀取用戶的所有數(shù)據(jù)id的需求。

          $redis->lrange('TEST_KEY', 0,9999);

          lpush命令可以實現(xiàn)新的數(shù)據(jù)id的寫入,ltrim可以保證將用戶的記錄數(shù)量不超過1萬條。

          $redis->lpush('TEST_KEY', 1,2,3,4,5,6,7,8,9,10);
          $redis->ltrim('TEST_KEY', 0,9999);

          我們準備一個用戶,提前存好一萬條id。寫入的時候每次只寫入10條新的id,讀取的時候通過lrange一次全部讀取出來。進行一下性能耗時測試,結(jié)果如下。

          Write repeats:10000     time consume:0.65939211845398   each 6.5939211845398E-5
          Rrite repeats:10000     time consume:42.383342027664    each 0.0042383342027664
          3
          Redis方案二:用string存儲 
          我能想到的另外一個技術(shù)方案就是直接用String來存。我們可以把1萬個int表示的數(shù)據(jù)id拼接成一個字符串,用一個特殊的字符把他們分割開。例如:"100000_100001_10002"這種。存儲的時候,拼接一下,然后把這個大字符串寫到Redis里。讀取的時候,把大字符串整體讀取出來,然后再用字符切割成數(shù)組來使用。

          由于用string存儲的時候,保存前多了一個拼接字符串的操作,讀取后多了一步將字符串分割成數(shù)組的操作。在測試string方案的時候,為了公平起見,我們把需要把這兩步的開銷也考慮進來。核心代碼如下:

          $userItems = array(......);

          //寫入
          for($i=0; $i<$repeats; $i++){
             $redis->set('TEST_KEY', implode('_', $userItems));
          }
          //讀取
          for($i=0; $i<10000; $i++){
             $items = explode("_", $redis->get('TEST_KEY'));
          }

          耗時測試結(jié)果如下

          Write repeats:10000     time consume:6.4061808586121    each 0.00064061808586121
          Read repeats:10000      time consume:4.9698271751404    each 0.00049698271751404
          4
          結(jié)論 

          我們再直觀對比下兩個技術(shù)方案的性能數(shù)據(jù)。

          圖1 方案1和方案二的性能對比

          基于list的方案里,寫入速度非??欤恍枰?.066ms,因為僅僅只需要寫入新添加的10條記錄就可以了,再加一次鏈表的截斷操作,但是讀取性能可就要慢很多了,超過了4ms。原因之一是因為讀取需要整體遍歷,但其實還有第二個原因。我們本案例中的數(shù)據(jù)量過大,所以Redis在內(nèi)部實際上是用雙端鏈表來實現(xiàn)的。

          圖2 Redis之雙端列表內(nèi)存結(jié)構(gòu)

          通過上圖你可能看出來,鏈表是通過指針串起來的。大量的node之間極大可能是隨機地分布在內(nèi)存的各個位置上,這樣你遍歷整個鏈表的時候,實際上大概率會導(dǎo)致內(nèi)存的隨機模式下工作。

          基于string方案在寫入的時候耗時比list要高,因為每次都得需要將1萬條全部寫入一遍。但是讀取性能卻比list高了10倍,總體上耗時加起來大約只有方案一的1/4左右。為什么?我們再來看下redis string數(shù)據(jù)結(jié)構(gòu)的內(nèi)存布局

          圖3 Redis之string內(nèi)存結(jié)構(gòu)

          可見,如果用string來存儲的話,不管用戶的數(shù)據(jù)id有多少,訪問將全部都是順序IO。順序IO的好處有兩點:

          • 1. 一內(nèi)存的順序IO的耗時大約只是隨機IO的1/3-1/4左右,

          • 2. 對于讀取來說,順序訪問將極大地提升CPU的L1、L2、L3的cache命中率

          所以如果你深入了內(nèi)存的工作原理,哪怕你不能直接去操作內(nèi)存,即使只是用一些開源的軟件,你也能夠?qū)⑺男阅馨l(fā)揮到極致~

          推薦閱讀:

          完全整理 | 365篇高質(zhì)技術(shù)文章目錄整理

          算法之美 : 棧和隊列

          主宰這個世界的10大算法

          徹底理解cookie、session、token

          淺談什么是遞歸算法

          專注服務(wù)器后臺技術(shù)棧知識總結(jié)分享

          歡迎關(guān)注交流共同進步

          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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视频 | ww国产 | 大色鬼AV |