如何高效的管理快取? LoopBuffer

2021-09-26 16:32:26 字數 2752 閱讀 9156

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

cppnet的資料流緩衝通過cbuffer類來實現,實際的資料儲存在cloopbuffer中,loop buffer實現如其名,通過在一塊固定大小的記憶體上移動指標來實現順序的讀寫操作。

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

當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請戳這裡。

如何快速高效的更新memcached快取資料

在高併發的分布式系統中,加入快取機制可以很大的提高效能。最近做的xx省的電信bss專案中就用到了memcached,系統中將查詢到的結果快取到memcached 中 下面稱mc好了 對於配置類的規格資料,是不經常改變的。在mc中的命中率高達90 以上。試想如果取消快取機制,每秒幾萬 幾十萬的資料訪問...

如何高效地管理時間

如何高效地管理時間 現代人的生活節奏越來越快,壓力也越來越大。經常會聽到白領人士抱怨乙個星期有三到四天的時間在加班,沒有時間鍛鍊身體,身體經常處在一種透支的狀態 也有人抱怨,雖然現在的職位已經到了中層管理層,但是沒有安全感,因為知識的更新速度太快。其實大家都感覺到時間是個瓶頸,每天列了一大堆的計畫,...

如何高效地管理時間

如何高效地管理時間 現代人的生活節奏越來越快,壓力也越來越大。經常會聽到白領人士抱怨乙個星期有三到四天的時間在加班,沒有時間鍛鍊身體,身體經常處在一種透支的狀態 也有人抱怨,雖然現在的職位已經到了中層管理層,但是沒有安全感,因為知識的更新速度太快。其實大家都感覺到時間是個瓶頸,每天列了一大堆的計畫,...