golang 源码剖析(2) TCMalloc内存管理器

本文详细介绍了Golang中的TCMalloc内存管理器,包括小对象和大对象的分配策略,Span的管理,内存回收机制以及线程级别的缓存。TCMalloc通过减少锁竞争和提高空间效率,实现了比ptmalloc2更快的内存操作性能,尤其在多线程环境中小对象的分配上。
摘要由CSDN通过智能技术生成

使用TCMalloc(Thread-Caching Malloc)当内存管理器
以下翻译自tcmalloc.html

动机

  1. 相对于glibc2.3 malloc, 在2.8GHz P4上,ptmalloc2需要大概300ns执行一个malloc/free 操作,TCMalloc只需要50ns
  2. 多线程时,可减少锁竞争. 小对象基本都是无锁,对于大对象,ptmalloc2也使用每个线程一个arena,但是在不同的arena上分配大对象时内存不会复用而会重新去申请内存
  3. TCMalloc在小对象上也是空间高效的,对N个8字节对象只需要N81.01bytes,而ptmalloc2每个对象使用4字节头,也就是12字节,对齐到8字节的倍数,从而使用了16N个字节

预览

TCMalloc为每个线程分配一个线程级别(thread-local)的cache.小的对象申请都可以直接从这个cache中获取,需要的话也可以将central的内存移到cache中,或者从cache中回收对象到central中
image.png
TCMalloc将小于32K的数据称为小对象(small objects).大对象直接通过页级别(page-level,以4k(41024byte)为单位对齐)的内存分配器从central heap中申请n页.
每页也可以切分成多个小对象,比如一页可切分为32个128字节的对象(32
128byte)

小对象分配

小对象可分为大约170种size-class,最大间距为256个字节
image
申请流程如下:

  1. 根据对象的大小找到合适的size-class
  2. 从当前线程的cache的空闲列表(free list)中查找,如果找到了就从空闲列表中移除并返回,这里甚至都用不到锁,这里也能稍微加速申请,毕竟一次lock/unlock对在2.8GHz Xeon处理器上也需要大约100ns的时间
  3. 如果空闲列表为空列表,则从central的空闲列表中(这里都是切割好的,可能是以前cache中回收的)查找这个size-class(注意:central空闲列表是所有线程共享的),如果找到了,就放到cache的空闲列表中,并从刚获取到的这个数组中返回一个对象
  4. 如果central中空闲列表也为空,则直接通过mmap或将heap中切分等获取连续的页,切割成对应size-class的对象列表,放入central的空闲列表中,然后再将一部分送到cache的空闲列表中返回

大对象分配

对于超过32K的对象,会先将大小对齐到页大小(4k)的倍数,然后交由central page heap来处理,
不同于空闲列表,page heap并未将页切分成对象数组,而是直接将其分成了256中页class,n<=255,构成了n页的一个列表(页为单位),剩余的同意放到256这个页class中.
image
分配时直接找到能满足的n页类型空闲列表,如果找不到则直接n++,找稍微大一些的空闲列表,如果失败了则从系统中通过mmap获取,如果找到的页过大,则会进行切分,将剩余的页插入到合适的空闲列表中去

Spans

TCMalloc通过连续的页称用span来管理heap. span有已申请或者空闲两种状态.
如果空闲,则span可以看做是heap某个链表的入口,可以看做是多个页打包在一起
如果已申请,则可能是一个使用中的大对象,或者切分成一系列的小对象列表供小对象使用
一个根据页编号的数组(central array)查到一个页是属于哪个span
一个32位的地址空间可以容纳2的20次方个4k的页面,这个central array占用了4MB的空间,在64位机器上,使用的是3级基数树(3-level radix tree,类似于二叉树)来查找对应的页
image

内存回收(Deallocation)

当某个对象要回收时,我们计算它的页编号(page number),并从central array中找到对应的span,这个span中能知道这个对象是不是小对象还有小对象对应的size-class,如果是小对象,则将其插入到对应cache中的空闲列表中去,如果这个线程的cache超过了一个预设的值(默认是2MB),将会出发垃圾回收将未使用的对象移入central的空闲列表中去.

central小对象的空闲列表

我们保存了每个size-class的空闲列表,每个空闲列表都有两级数据结构:一系列的span,每个span一个空闲对象的链表.
对象的申请通过从空闲列表中移除一个并返回(如果没有空闲链表,则会先从page heap中去申请)
对象的回收通过将其插入到它对应span的空闲列表中,如果链表长度已经等于span的最大对象的数量,则改span全部空闲,会将其回收到page heap中去

cache的垃圾回收

当这个cache中所有对象的长度超过预设的值时(默认2MB,随着线程数的增多,会自动降低这个阀值以免浪费)
会遍历所有cache的空闲列表,然后将一定数量的对象回收到对应的central的空闲列表中去
被回收的数量会根据每个列表中的L(low-water-mark)决定,L记录着上次回收时的最小长度.根据L会预测未来的使用量,并将L/2个对象移入central的空闲列表中去。这个算法的好处是当某个线程停止使用某个特定sizeclass的对象时,快速的移入central的空闲列表中,由于central的空闲列表是共享的,可直接供其他线程使用(较PTMalloc的优点之一)
### 性能

  • TCMalloc在大部分的申请大小上性能都较PTMaloc2更好,原因是在cache中的话会减少冲突
  • 随着分配尺寸的增大,由于cache垃圾回收的限制,导致大量的垃圾回收,很少有对象能存储在cache中
  • 当超过32k时性能下降变慢,这是由于32k之后算大对象,都是存储在page heap中了
    申请大小和每秒操作数关系图:
    image
    线程数与每秒操作数关系图
    image
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值