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

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

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

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

start : 指向分配內存的起始地址。
end: 指向分配內存的末端地址。
read: 當前讀取遊標。
write: 當前寫入遊標。
當loop buffer第一次被建立時,指針的位置如圖1:oop

圖1

start, read, write三個指針都指向內存的起始位置,這個時候 read = write 可讀取數據爲空。接下來進行數據寫入,如圖2:性能

圖2

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

接下來 咱們進行一次數據讀取, 如圖3:3d

圖3

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

接下來咱們將全部的數據讀取出來, 如圖4:cdn

圖4

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

接下來又有新的數據到來, 如圖5:

圖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請戳這裏

相關文章
相關標籤/搜索