菜鸟笔记
提升您的技术认知

tcmalloc内存分配与使用分析-ag真人游戏

(一)简介

        tcmalloc是与glibc、malloc同一级别的内存管理库,tcmalloc会hack所有glibc提供的接口,为调用者提供透明的内存分配。

(二)总体结构

  • pageheap

内存管理单位:span(连续的page的内存)

  • centralcache

内存管理单位:object(由span切成的小块,同一个span切出来的object都是相同的规格)

  • threadcache

线程私有的缓存,理想情况下,每个线程的内存需求都在自己的threadcache中完成,线程之间不需要竞争,非常高效。

内存管理单位:class(由span切成的小块)

  

(三)分配与回收

        基本思想:前面的层次分配内存失败,则从下一层分配一批补充上来;前面的层次释放了过多的内存,则回收一批到下一层次。

  • 分配

 

1)小块内存(<256kb)。

threadcache:先尝试在list_[class]的freelist分配。

centralcache:找到对应class的tc_slots链表,从链表中分配 -> 从nonempty_链表分配(尽量分配batch_size个object)

headpage:伙伴系统对应的npages的span链表 (normal->returned)-> 更大的npages的span链表,拆小

kernel:申请若干个page的内存(可能大于npages)

2)大块内存(>256kb)。

headpage:伙伴系统对应的npages的span链表 (normal->returned)-> 更大的npages的span链表,拆小

kernel:申请若干个page的内存(可能大于npages)

  • 回收

与申请流程类似

1)threadcache => centralcache

threadcache容量限额:

a、为每一个threadcache初始化一个比较小的限额,然后每当threadcache由于cache超限而触发object到centralcache的回收时,就增大限额。

b、预设所有threadcache的总容量,一个threadcache容量不够时,从其他threadcache收刮(轮询)。

c、每个threadcache也有最大最小值限制,不能无限增大限额。

d、每个threadcache超过限额时,对其每个freelist回收。

单个freelist的限额:

a、慢启动。初始长度限制为1,限额1~batch_size之间为慢启动,每次限额 1。

b、超过batch_size,限额按照batch_size整数倍扩展。

c、freelist限额超限,直接回收batch_size个object。

2)centralcache => pageheap

只要一个span里面的object都空闲了,就将它回收到pageheap。

3)pagehead中的normal => returned

a、每当pageheap回收到n个page的span时(这个过程中可能伴随着相当数目的span分配),触发一次normal到returned的回收,只回收一个span。
b、这个n值初始化为1g内存的page数,每次回收span到returned链之后,可能还会增加n值,但是最大不超过4g。
c、触发回收的过程,每次进来轮询伙伴系统中的一个normal链表,将链尾的span回收即可。

(四)数据结构

  • pageheap

1)page到span的映射关系通过radix tree来实现,逻辑上理解为一个大数组,以page的值作为偏移,就能访问到page对应的span节点。

2)为减少查询radix tree的开销,pageheap还维护了一个最近最常使用的若干个page到object的对应关系cache。为了保持cache的效率,cache只提供64个固定坑位。

3)空闲span的伙伴系统为上层提供span的分配与回收。当需要的span没有空闲时,可以把更大尺寸的span拆小;当span回收时,又需要判断相邻的span是否空闲,以便组合他们

4)normal和returned:多余的内存放到returned中,与normal隔离。normal的内存总是优先被使用,kernel倾向于一直保留他们;而returned的内存不常被使用,kernel内存不足时优先swap他们。

 

  • centralfreelist

1)维护span的链表,每个span下面再挂一个由这个span切分出来的object的链。便于在span内的object都已经free的情况下,将span整体回收给pageheap;每个回收回来的object都需要寻找自己所属的span后才挂进freelist,比较耗时。

2)empty的span链和nonempty的span链:centralfreelist中的span链表有nonempty_和empty_两个,根据span的object链是否有空闲,放入对应链表。如果span的内存已经用完则把这个span移到empty链表中。

3)通过页找到对应span:被centralfreelist使用的span,都会把这个span上的所有页都注册到radixtree中,这样对于这个span上的任意页都可以通过页id找到这个span。

4)如果span的内存已经完全被释放(span->refcount==0),则把这个span归还到pagehead中。

  • threadcache

1)tcmalloc为每个线程创建一个threadcache对象,当线程结束的时候,threadcache对象会随之销毁。

2)threadcache为每种类型的内存都保存了一个单项链表。

 

(五)适用场景

tcmalloc适用线程内小内存分配需求,一般情况下只有大空间分配才使用中央堆,中央堆分配回收我记得是需要加锁,成本高。

(六)使用遇到的坑

在一个项目中,使用thrift的threadpool模型 上游短连接 tcmalloc时,性能大幅下降,大概只有使用普通malloc的1/10。

效果对比图如下:

但是同样是使用tcmalloc,在短连接 多线程or长连接 线程池场景下,性能却不受影响:

可以确定,是 tcmalloc 短连接 thrift线程池模型,才会出现这个坑。

使用oprofile工具对事件:1)分支预测错误;2)cpu时钟;3)指令数;4)l2缓存未命中 采样监控,观察服务一段时间,如下:

可以看到,cpu时钟占用的排序为:

tcmalloc::centralfreelist::fetchfromspans(spans->centralfreelist)、

tcmalloc::centralfreelist::releasetospans(centralfreelist->spans)、

tcmalloc::threadcache::releasetocentralcache(threadcache->centralcache)、

spinlock::spinloop(分配pageheap时使用)、

tcmalloc::centralfreelist::releaselisttospans(centralfreelist->spans)

可以发现,服务过程中有频繁的centralfreelist与pageheap之间的内存申请and回收,而tcmalloc的pagehead申请是使用的spinlock锁,消耗大。

网站地图