日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

TCMalloc(Thread-Caching malloc) 基本设计原理

發布時間:2023/11/27 生活经验 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TCMalloc(Thread-Caching malloc) 基本设计原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 背景
    • 如何使用
    • 架構概覽
    • 1. TCMalloc Front-end
      • 1.1 小對象和大對象的內存分配過程
      • 1.2 內存釋放過程
      • 1.3 Per-CPU mode
      • 1.4 Per-thread mode
      • 1.5 per-cpu 和 per-thread 運行時內存管理算法對比
    • 2. TCMalloc Middle-end
      • 2.1 Transfer Cache
      • 2.2 Central Free List
      • 2.3 Pagemap 和 Spans
    • 3. TCMalloc Backend
      • 3.1 Legacy Pageheap
      • 3.2 大頁場景分配器設計
    • 4. 參考

背景

之前介紹了TCMalloc, Jemalloc, Glibc-malloc 的性能 以及 TCMalloc和Glibc Malloc 的大體架構和主要優勢:三種 Allocator 性能 以及 glibc/tcmalloc 設計差異。

本節將專注于TCMalloc 的架構設計細節,來整體看一下TCMalloc 的設計特性。

主要的幾個特性如下:

  1. 高性能。大多數對象的分配和釋放都不需要產生太多的競爭,因為tcmalloc 維護了thread-cache 來提供當前線程的內存分配需求。所以,應用大多數的內存申請需求不會有鎖的競爭,而且在多核場景有較好的擴展性。
  2. 靈活的使用內存資源。用戶不使用的內存,tcmalloc會選擇服復用或者歸還操作系統。
  3. 降低了每個請求的內存開銷。通過分配相同大小的page 降低內存使用的開銷,這在小對象場景較為有效。
  4. 內部信息統計開銷較低。能夠開啟細粒度的應用內存占用信息,來幫助用戶展示tcmalloc內部內存使用的細節。

如何使用

安裝tcmalloc,應用編譯加入 -ltcmalloc即可默認將glibc的malloc 替換為tcmalloc的malloc ,之前的文章中關于性能的對比測試中能夠直接看到。

架構概覽

前面的文章中在介紹整體的tcmalloc的時候已經介紹過了,大體分為三個部分:

  • Front-end,主要維護線程cache,用來為應用提供 快速分配以及釋放內存的需求。
  • Middle-end,主要負責為font-end 中的thread-cache 填充內存。
  • back-end,負責從os直接獲取內存。

需要注意的是front-end 的 thread-cache 可以在每一個cpu下維護 或者 每一個線程下維護,back-end` 則能夠支持大內存的pageheap管理,也能支持小內存的pageheap 管理。

接下來,我們進入每一個組件詳細看一下其設計細節。

1. TCMalloc Front-end

Front-end 提供了Cache ,能夠緩存一部分內存 用來分配給應用 ,也能夠持有應用釋放的內存。這個Cache 作為per-cpu/per-thread 存在,其同一時刻只能由一個線程訪問,所以本身不需要任何的鎖,這也是多線程下內存分配釋放高效的原因。

如果Front-end 持有的內存大小足夠,其能夠滿足應用線程任何內存需求。如果持有的內存為空了,那它會從 middle-end 組件請求一批內存頁進行填充。

Middle-end 是由 CentralFreeList 和 TransferCache 兩部分組成,后續會詳細介紹這兩個組件。

如果用戶請求的內存大小超過了front-end 本身能緩存的大小(大內存需求),或者middle-end 緩存的內存頁也被用盡了,那這個時候會直接讓從back-end分配內存給用戶。

Front-end 在 TCMalloc 的演進過程中有兩種類型的實現:

  1. 最開始的時候只支持 per-thread 的cache 模式,這也是TCMalloc 名字 Thread-Cacheing malloc的由來。然而這種場景會隨著用戶線程的大量增加,出現了一些內存問題:每個線程只能有極小的thread-cache,需要消耗較多的CPU資源來聚合每個線程的內存資源。

  2. 較新版本的TCMalloc 支持了 per-CPU 模式。這種模式下每一個邏輯CPU 會有自己的的thread-cache 用來為運行在這個cpu的現場分配內存。在x86系統上,每一個邏輯cpu 和 一個超線程等價,我們lscpu 命令看到的cpu core是包括超線程的個數在內的。

1.1 小對象和大對象的內存分配過程

針對小內存對象的分配,Front-end的cache 會按照其大小將其映射到 60-80個size-classes 中的一個,實際的內存分配會按照該大小對應的size-class 的大小進行分配,size-class也是front-end 分配內存的粒度。

比如12B 的對象會best-fit到16B 的size-class中。設置這么多的size-class 還是為了盡可能得降低內存的浪費,比如原本的內存分配粒度都是2的n次冪,那對于23字節的內存需求就需要分配一個32字節的內存區域,而在tcmalloc的size-class的配置中只需要分配24字節即可。

Tcmalloc 在編譯的時候可以配置 size-class 的 內存分配是按照多少 Bytes 對齊,比如指定了__STDCPP_DEFAULT_NEW_ALIGNMENT__ <= 8,那size-class 內部可選的內存大小配置就是 8bytes 對齊,即8 bytes的倍數。如果編譯時指定了大于 8bytes,那后續所有的 ::operator new 的內存申請都會按照 16 bytes 進行對齊。

對于大內存需求的對象來說,內存大小的需求超過kMaxSize 256K,則會直接從back-end 分配。因此,這一部分的內存需求不會緩存再 front-end 以及 middle-end 中,由back-end的page-heap 進行管理。對于大內存對象的實際內存分配會按照tcmalloc page size 進行對齊。

1.2 內存釋放過程

如果要釋放一個對象,編譯期間如果能夠知道這個對象的大小,編譯器會直接告訴分配器這個對象的大小。大多數的時候,編譯期間并不清楚對象的大小,會從pagemap中查找這個對象。如果這個對象是一個小內存對象,釋放的時候會告訴front-end cache進行管理,如果是一個超過kMaxSize 的對象,則會直接釋放給back-end 的 pageheap。

1.3 Per-CPU mode

Per-cpu mode 和 per-thread mode 是 tcmalloc 的font-end 主體部分的兩種模式。因為per-thread mode 受到系統進程的線程數的影響,在大量線程的情況下會讓每個thread-cache 只能夠處理一小部分的內存申請釋放需求,還會消耗大量的cpu 來 由middle-end 進行不同thread-cache 之間的內存遷移。

所以 tcmalloc 提供了優化版本的 per-cpu mode,即每一個邏輯核 維護一個 ‘cpu-cache’ ,用來處理運行在當前核的線程的內存申請/釋放需求。

大體形態如下:

Per-cpu mode 下會申請一個大內存塊(也可以稱為slab),這個slab 會被多個cpu共享,其中每一個cpu會 持有slab 的一部分內存,并在其上存儲一系列元數據管理對應線程的內存對象。

上圖中的cpu1 會管理 on slab 的綠色部分內存區域,在這一部分區域中會先存儲一些元數據和指針來管理不同大小的 size-classes 的內存對象。其中元數據中包含一個header指針 和 每一個size-class 的索引block。

每一個size-class 的header 指針數據結構會有指向某一種實際存儲內存對象的指針數組,即是一個數組,每一個元素是一個指針,總共是三個指針,分別指向這一種size-class 內存對象區域的起始地址塊,當前地址塊(后續分配這個size-class 大小對象的時候會從current 開始分配),最大地址。

每一個cpu 能夠緩存的內存大小是通過SetMaxPerCpuCacheSize 配置的,也就是當前font-end 能夠緩存的內存總大小取決去當前系統的cpu核心數,擁有更好核心數的機器使用tcmalloc 能夠緩存更多的內存。為了避免 perf-cpu 長時間持有內存,tcmalloc 允許通過MallocExtension::ReleaseCpuMemory 接口來 指定釋放某一個cpu的內存。

// 設置每一個cpu 能夠cache住的內存大小
void MallocExtension::SetMaxPerCpuCacheSize(int32_t value) {
#if ABSL_INTERNAL_HAVE_WEAK_MALLOCEXTENSION_STUBSif (MallocExtension_Internal_SetMaxPerCpuCacheSize == nullptr) {return;}MallocExtension_Internal_SetMaxPerCpuCacheSize(value); // 實際的執行函數
#else(void)value;
#endif
}// 釋放某一個cpu cache的內存
size_t MallocExtension::ReleaseCpuMemory(int cpu) {
#if ABSL_INTERNAL_HAVE_WEAK_MALLOCEXTENSION_STUBSif (MallocExtension_Internal_ReleaseCpuMemory != nullptr) {return MallocExtension_Internal_ReleaseCpuMemory(cpu);}
#endifreturn 0;
}

當每一個cpu cache 的內存被分配耗盡,想要從 middle-end 獲取內存來緩存更多的對象時,也需要考慮對size-class進行擴容。如果這個size-class 的內存分配需求還在持續增加,則這個size-class的容量會持續增加,直到達到這個size-class 容量的hard-code。

1.4 Per-thread mode

用戶可以指定使用某一個thread 的cache,也就是指定thread-local cache。小內存的申請和釋放會根據thread-cache的需求在middle-end 之間遷移。

每一個 thread-cache 內部 不同size-class 對象會各自構造一個單鏈表(如果有 n 個size-classes,也就會有對應 n 個單鏈表),類似如下圖:

分配某一個對應size-class 對象的時候,對應 size-class 鏈表對象會被從單鏈表移除(free-list),表示這個指針對應地址的內存可以被用戶使用。釋放對象的時候,則會將這個對象地址追加到thread-cache 管理的 size-class 的鏈表。

在這個過程中,如果thread-cache 管理的內存不夠,或者超限,則會從 middle-end 獲取更多的內存對象或者將多余的內存對象釋放給 middle-end。

對于per-thread caches來說,可以通過 MallocExtension::SetMaxTotalThreadCacheBytes 設置最大的可用內存大小。每一個線程有自己的最小的 thread-cache 大小 KMinThreadCacheSize 512K,如果當前線程內存申請需求較大,內存容量也會通過middle-end 將其他線程的可用內存遷移到當前線程。

通過 middle-end 來協調當前的thread-cache 內存,通過ThreadCache::Scavenge(); 進行。

如果當前線程退出,則會將自己的thread-cache 的內存返回給 middle-end。

1.5 per-cpu 和 per-thread 運行時內存管理算法對比

對于thread-cache 和 per-cpu cache來說,在應用程序運行的時候如果 front-end 的cache 內存太小,那就需要頻繁從 central-freelist 也就是 middle-end 中獲取內存;但如果太大,就會讓過多的內存被閑置。如何配置一個合理的 front-end cache 的大小,這里兩種模式都提供了動態配置 cache大小的算法。

  • Per-thread 模式下,cache 內部的最大存儲對象容量 達到當前最大閾值時就會從middle-end 獲取更多的對象,從而增大這個限制。降低最大限制的前提是發現了較多的未被使用的對象,則會將這一些對象根據需求還給middle-end。
  • Per-cpu 模式下,增加cache 容量的前提是當前cache 是否在頻繁的從 middle-end 獲取內存 以及 釋放內存交替,則需增加容量限制,有更多的空間來緩存這一些內存對象。降低容量限制的前提是發現有一些空閑容量長時間沒有被使用。

這里在代碼細節上有很多的設計,比如如何設置合理的空閑時間的長度?如何界定 內存申請是頻繁的?都是一些動態規劃的最優思想的設計,值得去探索代碼細節。

2. TCMalloc Middle-end

到此,font-end 部分大體設計描述完。從前面的設計中可以看到,middle-end 的作用在 per-cpu和per-thread 模式都有非常重要的作用。

Middle-end 的主要作用為 font-end 提供內存申請需求,并將空閑內存返回給 back-end。

Middle-end 的組成主要有 Transfer cache 和 Central free list。對于每一個size-class,都會有有一個各自的 transfer cache 和 central free list。這一些caches 會有自己的 mutex lock,所以對于這一些cache的訪問, 因為鎖粒度較低,則不會有過多的沖突,保證了訪問的性能。

2.1 Transfer Cache

當 front-end 請求內存 或者 釋放內存的時候,會先到達 transfer cache。

Transfer cache 會持有 一個數組指針進行內存的釋放 或者 將新的內存對象填充進來并返回給font-end。

Transfer cache 會將一個cpu/tthread 釋放的內存分配給另一個cpu/thread 對內存的需求,這個類似于內存池的 內存對象流動在兩個不同的cpu/threads 之間可以非常迅速。

2.2 Central Free List

Central free list 通過 span 數據結構來管理內存,一個span 可以管理一個或者多個tcmalloc page,span 數據結構會在下文詳細描述。

Font-end 如果從 central free list 請求一個或者多個內存對象的時候,central free list 會從span中提取對應大小的對象,如果此時span 沒有足夠的pages 返回,則會從back-end 請求更多的span。

當內存對象返回給central free list,則這一些對象會通過 pagemap 被映射到對應的span中進行管理,如果所有的對象都返回給span,這個span就可以被釋放給back-end.

2.3 Pagemap 和 Spans

tcmalloc 管理的堆內存會在編譯期間確定一個page-size,并將這么多內存映射為對應size的一個個page。一系列正在被使用的pages 可以被一個span 對象描述,一個span 對象可以管理一個大的內存對象,也可以按照size-class 管理多個小對象。

pagemap 則是用來查找一個內存對象屬于哪一個span的,或者申請一個指定size-class 的內存對象。pagemap 是一個 2層或者3層的 radix-tree。下圖展示了一個兩層的 page-map 如何管理 span的,其中 spanA 管理了2個page,spanB 管理了三個page。

Span 這個數據結構 在 middle-end中用來 管理回收的內存對象;在back-end 用來管理 對應大小的pages,負責給central-list 提供對應大小的span。

3. TCMalloc Backend

tcmalloc 的back-end 主要有三個工作線程:

  • 管理未使用的大塊內存區域
  • 負責從 os 中獲取內存,來滿足其他組件的內存需求
  • 負責將其他組件不需要返回的內存,還給os

還有兩個后端組件:

  1. legacy pageheap. 管理tcmalloc 的pages
  2. 管理大頁內存的 pageheap。用來提升應用程序大頁內存的申請需求,降低TLB的miss。

3.1 Legacy Pageheap

legacy pageheap 是一個數組的freelist,統一管理可用內存頁。數組的每一個節點是一個free list,也就是單鏈表。一般這個數組的大小 k < 256,對于第k 個數組元素來說,它的鏈表中的每一個節點都管理了 k 個pages。

如果想要申請 k 個pages,則直接查找這個數組的第k 個元素,從free list中取一個節點即可。如果這個free list是空的,那就查找下一個數組元素的free list,直到找到一個非空的free list。如果還是失敗,則直接mmap 獲取內存。

當一段連續的pages 被返回給了pageheap,則會根據當前pages 是否能夠形成一個連續的pages區域,然后串聯這一些pages 并根據page數量 添加到對應的free list中。

3.2 大頁場景分配器設計

針對hugepage 的分配器設計 是希望其能夠有效持有 hugepage 大小的內存塊,需要的時候能夠快速分配,不需要的時候也能在合理的內存占用情況下釋放給操作系統。

hugepage 在x86 系統上一般是2M 及 以上的大小,tcmalloc 的back-end 擁有三個不同的cache 來管理大頁內存的分配:

  1. filler cache。能夠持有hugepage ,并提供一些大頁內存的申請需求。類似于legacy pageheap,通過一些free lists 管理pages 那樣管理huge page,主要用于處理小于hugepage 大小的內存申請。
  2. region cache。用于大于hugepage 大小的內存申請,這個cache 允許分配多個連續的hugepage。
  3. hugepage cache。和region cache的功能有點重復,也是用于分配大于hugepage 的內存申請。

4. 參考

tcmalloc 設計 : https://google.github.io/tcmalloc/design.html

總結

以上是生活随笔為你收集整理的TCMalloc(Thread-Caching malloc) 基本设计原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。