Go语言——内存管理

栏目: Go · 发布时间: 6年前

内容简介:参考:

Go语言——内存管理

参考:

图解 TCMalloc

Golang 内存管理

Go 内存管理

问题

  1. 内存碎片:避免内存碎片,提高内存利用率。
  2. 多线程:稳定性,效率问题。

内存分配

Go语言——内存管理

内存划分

(512GB/8KB) * 指针大小8byte = 512M
512GB / (指针大小(8 byte) * 8 / 2) = 16G

分配细节

  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
}
Go语言——内存管理

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
Go语言——内存管理

heap


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

集创思维设计矩阵

集创思维设计矩阵

慈思远 / 电子工业出版社 / 2017-4 / 72.00元

《集创思维设计矩阵——写给互联网人的设计指南》总结了作者从业7年以来的设计经历,在大量企业所面对的设计问题基础上,提出了枪型思维,即如何给产品更准确的定位。 在定位准确的基础上加以设计,提出了设计中高维度融合低维度的设计思维,即设计者可以从商业逻辑推演到设计逻辑,让设计更加精确;又提出了设计和计算的博弈,指出在每一步创新的基础上,设计者一定要清晰地评判设计的代价。这样设计后的产品才是可以和企......一起来看看 《集创思维设计矩阵》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码