tcmalloc內存分配與使用分析


(一)簡介

        tcmalloc是與glibc、malloc同一級別的內存管理庫,tcmalloc會hack所有glibc提供的接口,為調用者提供透明的內存分配。

(二)總體結構

  • PageHeap

內存管理單位:span(連續的page的內存)

  • CentralCache

內存管理單位:object(由span切成的小塊,同一個span切出來的object都是相同的規格)

  • ThreadCache

線程私有的緩存,理想情況下,每個線程的內存需求都在自己的ThreadCache中完成,線程之間不需要競爭,非常高效。

內存管理單位:class(由span切成的小塊)

  

(三)分配與回收

        基本思想:前面的層次分配內存失敗,則從下一層分配一批補充上來;前面的層次釋放了過多的內存,則回收一批到下一層次。

  • 分配

 

1)小塊內存(<256KB)。

ThreadCache:先嘗試在list_[class]的FreeList分配。

CentralCache:找到對應class的tc_slots鏈表,從鏈表中分配 -> 從nonempty_鏈表分配(盡量分配batch_size個object)

HeadPage:伙伴系統對應的npages的span鏈表 (normal->returned)-> 更大的npages的span鏈表,拆小

kernel:申請若干個page的內存(可能大於npages)

2)大塊內存(>256KB)。

HeadPage:伙伴系統對應的npages的span鏈表 (normal->returned)-> 更大的npages的span鏈表,拆小

kernel:申請若干個page的內存(可能大於npages)

  • 回收

與申請流程類似

1)ThreadCache => CentralCache

ThreadCache容量限額:

a、為每一個ThreadCache初始化一個比較小的限額,然后每當ThreadCache由於cache超限而觸發object到CentralCache的回收時,就增大限額。

b、預設所有ThreadCache的總容量,一個ThreadCache容量不夠時,從其他ThreadCache收刮(輪詢)。

c、每個ThreadCache也有最大最小值限制,不能無限增大限額。

d、每個ThreadCache超過限額時,對其每個FreeList回收。

單個FreeList的限額:

a、慢啟動。初始長度限制為1,限額1~batch_size之間為慢啟動,每次限額+1。

b、超過batch_size,限額按照batch_size整數倍擴展。

c、FreeList限額超限,直接回收batch_size個object。

2)CentralCache => PageHeap

只要一個span里面的object都空閑了,就將它回收到PageHeap。

3)PageHead中的normal => returned

a、每當PageHeap回收到N個page的span時(這個過程中可能伴隨着相當數目的span分配),觸發一次normal到returned的回收,只回收一個span。
b、這個N值初始化為1G內存的page數,每次回收span到returned鏈之后,可能還會增加N值,但是最大不超過4G。
c、觸發回收的過程,每次進來輪詢伙伴系統中的一個normal鏈表,將鏈尾的span回收即可。

(四)數據結構

  • PageHeap

1)page到span的映射關系通過radix tree來實現,邏輯上理解為一個大數組,以page的值作為偏移,就能訪問到page對應的span節點。

2)為減少查詢radix tree的開銷,PageHeap還維護了一個最近最常使用的若干個page到object的對應關系cache。為了保持cache的效率,cache只提供64個固定坑位。

3)空閑span的伙伴系統為上層提供span的分配與回收。當需要的span沒有空閑時,可以把更大尺寸的span拆小;當span回收時,又需要判斷相鄰的span是否空閑,以便組合他們

4)normal和returned:多余的內存放到returned中,與normal隔離。normal的內存總是優先被使用,kernel傾向於一直保留他們;而returned的內存不常被使用,kernel內存不足時優先swap他們。

 

  • CentralFreeList

1)維護span的鏈表,每個span下面再掛一個由這個span切分出來的object的鏈。便於在span內的object都已經free的情況下,將span整體回收給PageHeap;每個回收回來的object都需要尋找自己所屬的span后才掛進freelist,比較耗時。

2)empty的span鏈和nonempty的span鏈:CentralFreeList中的span鏈表有nonempty_和empty_兩個,根據span的object鏈是否有空閑,放入對應鏈表。如果span的內存已經用完則把這個span移到empty鏈表中。

3)通過頁找到對應span:被CentralFreeList使用的span,都會把這個span上的所有頁都注冊到radixtree中,這樣對於這個span上的任意頁都可以通過頁ID找到這個span。

4)如果span的內存已經完全被釋放(span->refcount==0),則把這個span歸還到PageHead中。

  • ThreadCache

1)tcmalloc為每個線程創建一個ThreadCache對象,當線程結束的時候,ThreadCache對象會隨之銷毀。

2)ThreadCache為每種類型的內存都保存了一個單項鏈表。

 

(五)適用場景

tcmalloc適用線程內小內存分配需求,一般情況下只有大空間分配才使用中央堆,中央堆分配回收我記得是需要加鎖,成本高。

(六)使用遇到的坑

在一個項目中,使用thrift的threadpool模型+上游短連接+tcmalloc時,性能大幅下降,大概只有使用普通malloc的1/10。

效果對比圖如下:

但是同樣是使用tcmalloc,在短連接+多線程or長連接+線程池場景下,性能卻不受影響:

可以確定,是 tcmalloc+短連接+thrift線程池模型,才會出現這個坑。

使用oprofile工具對事件:1)分支預測錯誤;2)CPU時鍾;3)指令數;4)L2緩存未命中 采樣監控,觀察服務一段時間,如下:

可以看到,CPU時鍾占用的排序為:

tcmalloc::CentralFreeList::FetchFromSpans(spans->centralfreelist)、

tcmalloc::CentralFreeList::ReleaseToSpans(centralfreelist->spans)、

tcmalloc::ThreadCache::ReleaseToCentralCache(threadcache->centralcache)、

SpinLock::SpinLoop(分配pageheap時使用)

tcmalloc::CentralFreeList::ReleaseListToSpans(centralfreelist->spans)

可以發現,服務過程中有頻繁的CentralFreeList與PageHeap之間的內存申請and回收,而tcmalloc的PageHead申請是使用的spinlock鎖,消耗大。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM