1. 程式人生 > >golang 學習筆記 ---記憶體分配與管理

golang 學習筆記 ---記憶體分配與管理

Go語言——記憶體管理

參考:

圖解 TCMalloc

Golang 記憶體管理

Go 記憶體管理

問題

  1. 記憶體碎片:避免記憶體碎片,提高記憶體利用率。
  2. 多執行緒:穩定性,效率問題。

記憶體分配

  記憶體劃分
  • arena即為所謂的堆區,應用中需要的記憶體從這裡分配, 大小為512G,為了方便管理把arena區域劃分成一個個的page,每個page為8KB,一共有512GB/8KB個頁
  • spans區域存放span的指標,每個指標對應一個page,所以span區域的大小為(512GB/8KB) * 指標大小8byte = 512M
  • bitmap區域大小也是通過arena計算出來512GB / (指標大小(8 byte) * 8 / 2) = 16G,用於表示arena區域中哪些地址儲存了物件, 並且物件中哪些地址包含了指標,主要用於GC。

分配細節

  1. object size > 32K,則使用 mheap 直接分配。
  2. object size < 16 byte,不包含指標使用 mcache 的小物件分配器 tiny 直接分配;包含指標分配策略與[16 B, 32 K]類似。
  3. object size >= 16 byte && size <=32K byte 時,先使用 mcache 中對應的 size class 分配。
  4. 如果 mcache 對應的 size class 的 span 已經沒有可用的塊,則向 mcentral 請求。
  5. 如果 mcentral 也沒有可用的塊,則向 mheap 申請,並切分。
  6. 如果 mheap 也沒有合適的 span,則向作業系統申請。

span

可以看出span是一個非常重要的資料結構,每個span包含若干個連續的page。

小物件分配會在span page中劃分更小的粒度;大物件通過多頁實現。

size class

go1.10\src\runtime\sizeclasses.go

// class  bytes/obj  bytes/span  objects  tail waste  max waste
//     1          8        8192     1024           0     87.50%
//     2         16        8192      512           0     43.75%
//     3         32        8192      256           0     46.88%
//     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%

//    ...
//    65      28672       57344        2           0      4.91%
//    66      32768       32768        1           0     12.50%

上表中每列含義如下:

  • class: class ID,每個span結構中都有一個class ID, 表示該span可處理的物件型別
  • bytes/obj:該class代表物件的位元組數
  • bytes/span:每個span佔用堆的位元組數,也即頁數*頁大小
  • objects: 每個span可分配的物件個數,也即(bytes/spans)/(bytes/obj)
  • tail bytes: 每個span產生的記憶體碎片,也即(bytes/spans)%(bytes/obj)

上表可見最大的物件是32K大小,超過32K大小的由特殊的class表示,該class ID為0,每個class只包含一個物件。所以上面只有列出了1-66。

有點像裝箱演算法,按照規格分配,減少記憶體碎片。

struct

span是記憶體管理的基本單位,每個span用來管子特定的size class物件,根據size class,span將若干個頁分成塊進行管理。

go1.10\src\runtime\mheap.go

type mspan struct {
    next *mspan     // next span in list, or nil if none
    prev *mspan     // previous span in list, or nil if none
   
    startAddr uintptr // address of first byte of span aka s.base()
    npages    uintptr // number of pages in span
    
    nelems uintptr // number of object in the span.
    
    allocBits  *gcBits
    gcmarkBits *gcBits
    
    allocCount  uint16     // number of allocated objects
    spanclass   spanClass  // size class and noscan (uint8)
    
    elemsize    uintptr    // computed from sizeclass or from npages
}
  10

以size class 10為例,npages=1,nelems=56,spanclass=10,elemsize=144;startAddr指arena區位置;next和prev指spans區,span連結串列;allocBits是一個bitmap,標記分配塊分配情況,這個設計我也用過,之前用redis bitmap實現了IPAM。

cache

從上面我們知道go通過span來分配記憶體,那在哪裡用span?通過之前的學習Go語言——goroutine併發模型,我們知道每個P都有mcache,通過mcache管理每個G需要的記憶體。

go1.10\src\runtime\mcache.go

type mcache struct {
   tiny             uintptr
   tinyoffset       uintptr
    
   alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
}

numSpanClasses = _NumSizeClasses << 1
_NumSizeClasses = 67

alloc是span陣列,長度是67 << 1,說明每種size class有2組元素。第一組span物件中包含了指標,叫做scan,表示需要gc scan;第二組沒有指標,叫做noscan。提高gc scan效能。

mcache初始沒有span,G先從central動態申請span,並快取在cache。

central

go1.10\src\runtime\mcentral.go

type mcentral struct {
   lock      mutex
   spanclass spanClass
   nonempty  mSpanList // list of spans with a free object, ie a nonempty free list
   empty     mSpanList // list of spans with no free objects (or cached in an mcache)

   // nmalloc is the cumulative count of objects allocated from
   // this mcentral, assuming all spans in mcaches are
   // fully-allocated. Written atomically, read under STW.
   nmalloc uint64
}
  • lock: 多個G併發從central申請span,所以需要lock,保證一致性
  • spanclass : 每個mcentral管理著一組有相同size class的span列表
  • nonempty: 指還有記憶體可用的span列表
  • empty: 指沒有記憶體可用的span列表
  • nmalloc: 指累計分配的物件個數

執行緒從central獲取span步驟如下:

  1. 加鎖
  2. 從nonempty列表獲取一個可用span,並將其從連結串列中刪除
  3. 將取出的span放入empty連結串列
  4. 將span返回給執行緒
  5. 解鎖
  6. 執行緒將該span快取進cache

執行緒將span歸還步驟如下:

  1. 加鎖
  2. 將span從empty列表刪除
  3. 將span加入nonempty列表
  4. 解鎖

heap

central只管理特定的size class span,所以必然有一個更上層的資料結構,管理所有的sizeclass central,這就是heap。

go1.10\src\runtime\mheap.go

type mheap struct {
   lock      mutex
   
   spans []*mspan

   // Malloc stats.
   largealloc  uint64                  // bytes allocated for large objects
   nlargealloc uint64                  // number of large object allocations
   largefree   uint64                  // bytes freed for large objects (>maxsmallsize)
   nlargefree  uint64                  // number of frees for large objects (>maxsmallsize)
    
   // range of addresses we might see in the heap
   bitmap        uintptr // Points to one byte past the end of the bitmap
   bitmap_mapped uintptr

   arena_start uintptr
   arena_used  uintptr // Set with setArenaUsed.

   arena_alloc uintptr
   arena_end   uintptr

   arena_reserved bool

   central [numSpanClasses]struct {
      mcentral mcentral
      pad      [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
   }
}
  • spans:對映span -> page
  • large:大物件,>32K
  • bitmap: gc
  • arena: arena區相關資訊,pages,堆區
  • central:通過size class管理span,每種size class對應兩個central
  heap