1. 程式人生 > 其它 >go記憶體管理

go記憶體管理

技術標籤:golang記憶體管理malloc

golang記憶體管理

前言

golang實現了自己的記憶體管理,在研究切片擴容的時候,有一步roundsize調整記憶體大小方法,隨後對golang中的記憶體管理深入學習了下,固記錄。

概念

在程式啟動的時候,golang會預先向處理器申請,如下虛擬地址空間(並沒有真正的申請),主要將其規劃給spans、bitmap、arena三部分

+-----------------------------------------------------------+
|   spans	|		bitmap		|			arena		    |
+-----------------------------------------------------------+

arena:就是說的記憶體堆區,真正分配記憶體的地方,大小為512G,內部由多個page分割,page是記憶體儲存的基本單元(8KB大小)

spans:存放多個*span,span是記憶體管理的基本單元,後文提及。需要確保每個span都能夠指向不同的page位置,所以spans的大小為(512G/8KB)*每個指標大小8B=512M

bitmap:儲存gc相關的點陣圖資訊

object:程式碼中等待分配的具體物件,在arena區域分配記憶體(一個object可能比page大也可能比page小)

策略

golang中的記憶體分配策略主要參考了C語言中的tcmalloc,將儲存物件(object)分為大小不同的多種,分別進行管理,減少記憶體碎片的問題,在golang中主要劃分為67種類別class

// class  bytes/obj  bytes/span  objects  tail waste  max waste
//     1          8        8192     1024           0     87.50% 7/8
//     2         16        8192      512           0     43.75% 7/16
//     3         32        8192      256           0     46.88% 15/32
//     4         48        8192      170          32     31.52% ....
//     5         64        8192      128           0     23.44%
//     6         80        8192      102          32     19.07%
//     7         96        8192       85          32     15.95%
//     8        112        8192       73          16     13.56%
//     9        128        8192       64           0     11.72%
//    10        144        8192       56         128     11.82%
//    11        160        8192       51          32      9.73%
//    12        176        8192       46          96      9.59%
//    13        192        8192       42         128      9.25%
//    14        208        8192       39          80      8.12%
//    15        224        8192       36         128      8.15%
//    16        240        8192       34          32      6.62%
//    17        256        8192       32           0      5.86%
//    18        288        8192       28         128     12.16%
//    19        320        8192       25         192     11.80%
//    20        352        8192       23          96      9.88%
//    21        384        8192       21         128      9.51%
//    22        416        8192       19         288     10.71%
//    23        448        8192       18         128      8.37%
//    24        480        8192       17          32      6.82%
//    25        512        8192       16           0      6.05%
//    26        576        8192       14         128     12.33%
//    27        640        8192       12         512     15.48%
//    28        704        8192       11         448     13.93%
//    29        768        8192       10         512     13.94%
//    30        896        8192        9         128     15.52%
//    31       1024        8192        8           0     12.40%
//    32       1152        8192        7         128     12.41%
//    33       1280        8192        6         512     15.55%
//    34       1408       16384       11         896     14.00%
//    35       1536        8192        5         512     14.00%
//    36       1792       16384        9         256     15.57%
//    37       2048        8192        4           0     12.45%
//    38       2304       16384        7         256     12.46%
//    39       2688        8192        3         128     15.59%
//    40       3072       24576        8           0     12.47%
//    41       3200       16384        5         384      6.22%
//    42       3456       24576        7         384      8.83%
//    43       4096        8192        2           0     15.60%
//    44       4864       24576        5         256     16.65%
//    45       5376       16384        3         256     10.92%
//    46       6144       24576        4           0     12.48%
//    47       6528       32768        5         128      6.23%
//    48       6784       40960        6         256      4.36%
//    49       6912       49152        7         768      3.37%
//    50       8192        8192        1           0     15.61%
//    51       9472       57344        6         512     14.28%
//    52       9728       49152        5         512      3.64%
//    53      10240       40960        4           0      4.99%
//    54      10880       32768        3         128      6.24%
//    55      12288       24576        2           0     11.45%
//    56      13568       40960        3         256      9.99%
//    57      14336       57344        4           0      5.35%
//    58      16384       16384        1           0     12.49%
//    59      18432       73728        4           0     11.11%
//    60      19072       57344        3         128      3.57%
//    61      20480       40960        2           0      6.87%
//    62      21760       65536        3         256      6.25%
//    63      24576       24576        1           0     11.45%
//    64      27264       81920        3         128     10.00%
//    65      28672       57344        2           0      4.91%
//    66      32768       32768        1           0     12.50%

class:類別

bytes/obj:該class類別每個物件的大小

bytes/span:該class類別span的總大小

objects:可以存放的物件數 (bytes/span)/(bytes/obj)

max waste:最大浪費空間,example:大小為1B的物件為其分配8B的記憶體空間,浪費7/8=87.5%

bytes/obj滿足8*2n的關係,至於為什麼是這個關係?設計考慮到整體max waste需小於某個值,在golang中這個值為多少沒有具體瞭解

span

span是記憶體管理的基本單元,由1個或者多個page組成,page個數在go中也是寫死的

// runtime/sizeclasses.go	
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}
// runtime/mheap.go
type mspan struct {
	next *mspan
	prev *mspan
	list *mSpanList

	startAddr uintptr // mspan在areana中的開始地址
	npages    uintptr // span的page數

	freeindex uintptr // 下一個可使用object的索引
	nelems    uintptr // span中object的個數

	spanclass spanClass // class
	elemsize  uintptr   // class表中的物件大小,也即塊大小

	allocBits  uintptr //分配點陣圖,每一位代表一個塊是否已分配
	allocCount uint16  // 已分配塊的個數
}

span在原始碼中的資料結構對應mspan,只寫了一部分比較重要的欄位,後文原始碼展示同理。

可以看出mspan是一個雙向的連結串列,儲存其在arena中開始位置startAddr、分配頁數npages、span中存放的object資訊等…

spans中*span指標就指向該結構體

基本單元

記憶體管理主要通過mcachemcentralmheap進行配合

mcache

// runtime/mcache.go
type mcache struct {

	// 每種類別是一個連結串列
	// class0 scan class0 noscan
	//	 | 			 |
	// 	 |			 |
	// class0 scan class0 noscan

	// 每一個mcache有134種mspan(67個指標供指標)
	// 每一個mspan可以分配多個object
	alloc [67*2]*mspan
}

mcache就是管理mspan的基本單元,alloc欄位為長度67*2的的*mspan陣列,大小為classsize的兩倍,原因是go在處理記憶體時將指標物件和非指標物件區分處理了,對於包含指標的物件需要進行掃描操作scan,不包含指標的物件不需要進行多餘的掃描操作noscan,也是一種效能優化方案。每個class對應一個span連結串列,需要注意的是該連結串列對應的都是該class的span,如註釋中那樣。

在程式執行時每個執行緒P會與一個mcache進行繫結(GMP排程模型),所以mcache不存在鎖問題,在P執行初始是不存在任何span的

mcentral

mcentral是記憶體分配的中轉站(central),每個class對應一個mcentral。當某個mcache無記憶體可用時到相應class的mcentral中獲取,所以mcentral是被多個執行緒共享的,需要鎖

// runtime/mcentral.go
type mcentral struct {
	// 多個goroutine共享的拿mspan所以需要鎖
	lock      sync.Mutex
	spanclass spanClass

	// 維護兩個雙向連結串列
	// 當有mcache申請一個mspan時
	// 遍歷連結串列查詢可利用的mspan
	// 當沒有mspan可用時,mcentral向mheap申請並返回給mcache
	nonempty mSpanList // 還有空閒物件或者已經被mcache快取的mspan
	empty    mSpanList // 沒有空閒物件的mspan聯表
}

mcentral維護兩個雙向連結串列nonempty還有空閒物件的span連結串列和empty沒有空閒物件的span連結串列。

mheap

mheap.central欄位儲存67*2個mcentral,程式執行開始預先分配的bitmapspansarena就是通過mheap進行管理,mheap作為上層結果管理所有的記憶體空間

// runtime/mheap.go
type mheap struct {
	// mheap對所有執行緒共享,固需鎖
	lock sync.Mutex

	allspans []*mspan // 所有mspan

	bitmap uintptr //指向bitmap首地址,bitmap是從高地址向低地址增長的

	arena_start uintptr //指示arena區首地址
	arena_used  uintptr //指示arena區已使用地址位置

	// heap由134個mcentral組成,67個無指標mcentral
	// 67個有指標mcentral
	// 每個class 一個mcentral
	central [134]struct {
		mcentral mcentral
	}
}

每個class類別對應一個mcentral,一共有67*2(scan和noscan兩種)種class也就是134種mcentral

mheap對所有執行緒共享固需加鎖

記憶體分配流程

前文提及的物件大小劃分圖種只列出了66種,go種將物件大小劃分為67種,最後一種為class為0的類別,bytes/obj>32KB的物件,此種物件稱為大物件,直接由mheap進行分配、另外就是小於16B的物件稱為tiny物件,go會使用tinyalloc分配器進行分配,本文著重講[16B,32KB]的物件的記憶體分配管理

當執行緒需要獲取大小為N的記憶體時:

  1. 判斷N大小屬於兩個class類別區間,向上取整獲取需為其分配的記憶體空間大小,example需要為bool型別的1B大小分配記憶體空間向上取整為8B(所以會有一部分記憶體的浪費)
  2. 獲取當前執行緒P繫結的mcache
  3. 通過物件大小獲取class ID,在mcache.alloc[class]中獲取相應class的span連結串列,判斷是否還有可用的object空間
  4. 如果沒有可用的span空間則向相應class的mcentral申請span

執行緒從mcentral獲取span:

  1. mcentral加鎖
  2. mcentral從nonempty中取出可用span並刪除,empty中新增相應span,如果沒有可利用的span則向mheap獲取
  3. mcentral返回span

mcache拿到可利用span則獲取空閒object區域,返回地址

執行緒將span返還給mcentral過程:

  1. mcentral加鎖
  2. 從empty中刪除相應span,nonempty中新增相應大小span
  3. mcentral解鎖

總結

  1. go程式啟動時預先申請一片虛擬地址空間自行管理
  2. 將地址空間分為bitmapspansarena三個區域,三片區域都通過mheap資料結構進行統一管理
  3. spans儲存指向mspan的指標,mspan記憶體管理的基本單位,主要以連結串列方式展現。bitmap儲存gc相關的點陣圖資訊,比如哪些被gc掃描過,哪些沒有。arena是就是儲存物件的地址堆區
  4. span由一個或多個頁page組成,在go中已經被定死了,真正儲存的物件為object,被分為大小不同的67種
  5. mcache與執行緒繫結,執行緒需要獲取記憶體時首先從mcache獲取,不存在鎖
  6. mcentral供多個執行緒共享span,當mcache沒有可用span時從mcentral獲取

參考資料

[圖解go記憶體分配]:https://juejin.cn/post/6844903795739082760

[Go’s Memory Allocator - Overview]:https://andrestc.com/post/go-memory-allocation-pt1/