如何高效的管理緩存?——LoopBuffer

  我們需要一種緩存結構,可以未預知數據大小的情況下高效的管理內存。每次數據到來的時候都能保證有效的寫入,即使動態的擴展內存也不會對原有的數據進行任何挪移操作。讀取數據的時候只能順序的讀取,也不會對未讀取到的數據進行移動。

  CppNet的數據流緩衝通過CBuffer類來實現,實際的數據存儲在CLoopBuffer中,loop buffer實現如其名,通過在一塊固定大小的內存上移動指針來實現順序的讀寫操作。

  每個loop buffer都持有一塊來自內存池的固定大小的內存。然後通過四個指針來嚴格標識數據的位置,注意這裏是嚴格標識,所以我們申請到的內存不用memset初始化,每次讀寫通過移動指針來控制數據流動,下面着重說下指針的幾種移動情況:

  • start : 指向分配內存的起始地址。
  • end: 指向分配內存的末端地址。
  • read: 當前讀取遊標。
  • write: 當前寫入遊標。

  當loop buffer第一次被創建時,指針的位置如,圖1:
在這裏插入圖片描述
  start, read, write三個指針都指向內存的起始位置,這個時候 read = write 可讀取數據爲空。接下來進行數據寫入圖2:
在這裏插入圖片描述

  write指針開始向右移動,記錄着下一次寫入的位置。現在可讀取的數據量是 write - read, 剩餘可寫入的內存大小是 end - write。

  接下來 我們進行一次數據讀取,圖3:
在這裏插入圖片描述

  read 指針開始向右移動,讀取到的數據量是 read - start,剩餘可讀取數據大小是 write - read,剩餘可寫入的內存大小依然是 end - write + (read - start)。

  接下來我們將所有的數據讀取出來,圖4:
在這裏插入圖片描述

  read 指針向右移動直到追上了write,現在read == write,當讀寫指針相等的時候,有兩種情況,要麼是內存被寫滿,要麼是內存塊爲空,需要一個額外的成員變量來標識。現在read追上了write,內存塊爲空,可讀取數據大小是0,可寫大小是整個內存塊的大小,爲了使可寫緩存更爲完整,以方便writev和readv的調用,每次read指針追上write指針的時候,我們都將所有指針狀態重置,恢復到圖1的狀態。

  接下來又有新的數據到來,圖5:
在這裏插入圖片描述

  我們看到 write 到了read 的左邊,這是因爲write 一直向右移動的時候,當指向了 end 指針,則需要重新調整指向 start,這就是loop的由來,而此時read 和 start之間有不少的距離,我們接着從start開始寫入數據,write又重新開始向右移動。現在可讀數據大小是 end - read + (write - start), 可寫數據大小是 read - write。

  如果接下來還是數據寫入的話, write 就會向右移動一直追上 read。這時 read == write, 但是內存已經被寫滿了。
  爲了配合readv的調用,需要有一個接口能返回當前可寫內存的起始位置和大小,通過上述的幾個過程我們可以觀察到有兩種情況:

    1:圖1,圖2,圖5的時候(圖4狀態會被重置爲圖1),只有一個可寫緩存區,起始地址是write指針,長度是read - write 或 end - write。
    2:圖3的時候有兩個可寫區域,起始地址是write和start,可寫長度分別是end - write 和 read - start。
  writev時需要返回所有的數據區域,與上述情況類似但操作的指針剛好相反,不再詳述。
  以上的幾個過程就是loop buffer寫入和讀取的全部情況,可以看到每次數據寫入和讀取的時候只有必要數據的複製,並沒有對其他數據的移動拷貝操作。

  但是loop buffer只有固定大小的內存,若是寫滿了之後還有新的數據寫入請求怎麼辦?這就是buffer表演的時候了。

  CBuffer實現上其實和CLoopBuffer非常的相似,也是通過四個指針來控制數據的讀取和寫入,甚至每個指針的作用都與其相同,只不過CLoopBuffer中指針指向的內存塊的具體位置,而CBuffer中的指針指向的是CLoopBuffer內存塊。其內部通過一個單向鏈表管理所有的內存塊節點,當有數據未滿的時候,幾個指針的移動操作和loop buffer的指針完全相同。

  唯一不同的是,當所有的內存塊被寫滿的時候,read == write,這時CBuffer需要重新從內存池中申請新的內存塊,並將其添加到鏈表中。
  以上的實現方式存在一個問題,CLoopBuffer的指針不是順序申請的,無法通過比較指針地址來判斷讀寫的先後順序,所以每個CLoopBuffer在實現的時候都攜帶了一個自身所處隊列的索引,每次查找的時候都需要重載操作符<或>的調用來判斷順序關係,valgrind性能分析時發現這裏調用頻次極高,所以重新優化了CBuffer的時候。

  重構之後的CBuffer用一個單向鏈表來管理loop buffer,寫入數據的時候如何空間不夠,則從內存池中申請新的節點添加到鏈表後邊,write指針向後移動。讀取數據的時候,一旦當前loop buffer節點的數據全部讀取完成,則將當前塊歸還給內存池,read指針向後移動。實現起來像是紅白機遊戲裏的馬里奧過浮橋,每次踩過的磚塊都會析構掉,前邊會生成新的磚塊拼成浮橋。整個讀寫過程都是從左往右順序移動的過程。
  以上就是CppNet緩存管理的核心實現。
   github請戳這裏

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