基於時間的定製分配器
2018-11-17
根據業務特點定製的分配器,絕對是最高效的實現之一。比如說,傳送網路訊息包需要拼湊很多物件來生成包,傳送完成後,訊息包的那塊記憶體就不再使用了。於是,這裡可以申請一塊記憶體反覆使用。相比於每次申請釋放記憶體,無論下層的記憶體管理如何實現,是不可能抵得上重用來得高效的。比如說,業務本身是無鎖的,特定實現相比通用的實現就簡單的多。
通用的分配器則難以利用到業務特點。通用實現一般是基於地址做回收的,廣義概念的回收,不管是 GC 還是 free 的回收。基於地址回收就涉及原記憶體裡面挖了洞,於是要考慮有各式各樣大小的物件池子去優化,要考慮回收的時候物件合併,考慮匹配大小的物件,還有內部碎片外部碎片這些。於是有了 freelist,buddy 演算法,bitmap 等等很多東西。
我們有一個業務場景,記憶體分配比較有特點。物件不停的分配,分配出來之後,會存活一段時間,再之後,就很少被使用了。不同於網路訊息那種一次的互動的簡單例子,它是一段時間內的存在較多互動,存活一段時間。另外一點,在整個存活時間內,記憶體使用量比較大,小物件特別的多,會對 GC 那邊掃描造成非常大的壓力。主要還是想優化小物件數量,於是,我設計了一個基於時間的定製分配器。
介面的設計上面,只暴露兩個函式:
func alloc(size int) ([]byte, int) func gc(safeTS int)
注意alloc
函式的返回值,除了分配的記憶體本身,它還額外返回了一個 timestamp。這就是這個物件的分配時間(概念上的時間,只是一個單調遞增的數字而已)。
然後是gc
函式,它會回收掉所有在 safeTS 時間之前的物件。
業務使用的時候,必須自己去處理還在使用中的物件,比如說重新分配一下,重新整理一下時間,有點像 lease 的續租。然後業務自己去呼叫gc
將舊的物件全部釋放掉。分配和釋放都是由業務自已來控制的。
業務怎麼知道哪些物件還會被使用呢? 可以把物件的建立時間存到物件裡面。呼叫gc
之前,用來跟 safeTS 去比較。如果物件建立時間小於 safeTS,並且它還需要繼續被使用,那麼就需要重新為它分配,然後讓使用者更新對它的引用。被使用者引用在有些業務場景是不知道的,於是就不能這麼幹了,這是一個限制。當然我們的場景是可以的。
然後實現細節。有一些記憶體塊,比如說每塊 1M(隨便拍腦袋定的)。分配時從當前記憶體塊裡面加劃出記憶體。
回收時,整塊記憶體做回收。每次操作時間會加加。每個記憶體塊上面,會記錄一個 minTS。注意有個約束條件,所有在這塊記憶體裡面劃出去的物件,它們對應的建立時間都是大於 minTS 的。
block1(minTS=0) obj(ts=1) obj(ts=2) ... obj(ts=29) -> block2(minTS=30) obj(ts=31) obj(ts=32) ... -> block3(minTS=77) ... ->
整個過程沒有刪除操作,直到開始回收。把整個舊的大塊記憶體直接刪除。假設我們要回收到safeTS = 44
,那麼直接刪除掉 block1 就好了,因為所有 block1 上面的物件的時間肯定是處於[1, 30)
的,小於 safeTS。
不像 GC 做標記清掃,這個演算法不會根據物件地址去標記,清掃也不用重新整理記憶體,非常高效。假設物件過了一定時間後都是不再使用的。業務自己要去處理將活著的物件挪走。
大半夜寫部落格pingcap/tidb/pull/8344/files#diff-4931e8b3fc11f830a7973b15df9e5efeR59" rel="nofollow,noindex" target="_blank">寫程式碼 ,感覺頭腦都不太清醒了...