騰訊開源框架TarsCpp-rpc設計分析-server(四)

4 TC_Buffer的妙用

客戶端收發包流程的緩存都用到了TC_Buffer結構,利用“水位”完成了內存的動態管理。本章對其進行介紹

4.1 TC_Buffer的整體結構

TC_Buffer的本質是一個字符串char* _buffer,我們使用_readPos讀遊標和_wirtePos寫遊標標明瞭已讀數據和未讀數據,使用_capacity標明整個_buffer當前大小。如下圖所示:

騰訊開源框架TarsCpp-rpc設計分析-server(四)

TC_Buffer結構.png

  • 紅色方框裡是已讀數據,綠色方框是未讀數據,淡紫色方框是已申請但未佔用的空間

4.2 TC_Buffer原理說明

還是以具體場景說明TC_Buffer的工作原理。

(1)TC_Buffer初始化時,_readPos、_writePos、_capacity都為0,_buffer為NULL

(2)通過PushData放入第一筆數據data,長度為size,TC_Buffer默認_capacity為128字節。這時分兩種情況:

  • 如果size < _capacity,直接創建大小為128字節的_buffer,然後將data拷貝到_buffer中,並調整寫遊標位置。如下圖:
騰訊開源框架TarsCpp-rpc設計分析-server(四)

小於128字節.png

  • 如果size > _capacity,即size大於128字節,則_capacity會以2的n次方形式增長(跟vector的內存管理很像),直到_capacity > size。例如size為200,_capacity = 2的8次方= 256字節。如果size為300,_capacity = 2的9次方 = 512字節。當size = 200時候,_buffer如下圖:
騰訊開源框架TarsCpp-rpc設計分析-server(四)

大於128字節.png

(3) 假設第2步已經放入了size為200的數據data,現在通過PopData讀取長度為size1 = 100的數據。完成後,_buffer的遊標數據如下:

騰訊開源框架TarsCpp-rpc設計分析-server(四)

PopData.png

(4)上面第3步後,_buffer的緩衝區還剩下256 - 200 = 56 字節的空間(淡紫色方框)。這時通過PushData放入第二筆數據data2,同樣分兩種情形,數據長度size2 > 56;數據長度size2 < 56

  • size2 = 60,大於56。這時_capacity = 256需要再翻倍,變成512字節。同時放棄原有老_buffer,把之前未讀內容(長度為_writePos - _readPos)拷貝到新的_buffer上。如下圖:
騰訊開源框架TarsCpp-rpc設計分析-server(四)

第二次PushData大於56.png

  • size2 = 50,小於 56。會利用::memmove方法把未讀內容重新拷貝到原有_buffer第一位,覆蓋掉已讀內容。然後加上新的data2內容。如下圖:
騰訊開源框架TarsCpp-rpc設計分析-server(四)

第二次PushData小於56.png

(5)第5步我們單獨討論Shrink。可能有小夥伴已經發現了,如果像上面步驟一直PushData PopData,_capacity會一直增加不會減少,即使數據都已經被讀取完了。這時候Shrink就派上用場了。

  • 如果_capacity小於128默認字節,不會進行內存調整。
  • 如果未讀數據大於整體容量和水位(_highWaterPercent)乘積,不會進行內存調整,反之會進行。
  • 例如未讀數據size = 150(_writePos - _readPos),整體容量_capacity = 256,_highWaterPercent為50,會進行下面的判斷:150 100 > 256 50,即不進行內存調整
  • 例如未讀數據size = 100(_writePos - _readPos),整體容量_capacity = 256,_highWaterPercent為50,則有100 100 < 256 50,這時候進行內存調整。調整策略是:使用函數RoundUp2Power創建一個大小為128(2 2 2 2 2 2 2)的新_buffer,將未讀數據拷貝到新_buffer,後續使用這個新_buffer.
騰訊開源框架TarsCpp-rpc設計分析-server(四)

Shrink.png

4.3 小結

  • Shrink()函數的使用時機是人為控制的,可以設置各種觸發條件,如未讀數據大小為0時候,或者整體容量_capacity大於某個數值時候
  • 每次的PushData相當於清理了已讀數據,釋放出了這部分空間供新數據使用
  • 當新數據很長,超過了當前的可用空間(_capacity - _writePos)時,整體容量_capacity的擴容策略是取大於當前_capacity的最小的2的n次方的值。這樣的好處是預先分配了內存空間,可以減少內存的頻繁申請。
  • _highWaterPercent,即水位值。在Tars RPC取的默認值是50,即未讀數據的長度大於整體容量一半時候,是不調整內存的,而小於整體容量一半時候,說明空間比較浪費,需要重新調整內存了。


分享到:


相關文章: