动机
c++的分配器malloc和free,在功能上保证内存分配的正确性
然而在性能上,却有明显的缺陷
- 过多的系统调用
- 频繁申请和释放内存
- 内存碎片导致性能恶化
为了解决上面的问题,提出了很多内存分配的第三方库
其中比较出色的有tcmalloc和jemalloc
使用了它们,mysql的性能提高了30%
其中jemalloc具有更好的内存碎片管理,使得在内存局部性得到更大的发挥
tcmalloc介绍
tcmalloc全名Thread-Caching Malloc
表示以线程cache来维护内存分配
这样做可以减少锁或者原子变量的竞争
使得可以快速的申请和释放内存
Overview
- Front-end是一个内存池,为了给应用快速的申请和释放内存的
- Middle-end是用来整理内存的,它使用原子锁,来保证线程安全,并给Front-end提供内存和回收内存
- Back-end是面向OS申请和释放内存的
Front-end
tcmalloc的Front-end是给应用快速申请和释放内存的
它的模式可以是per-thread cache或者per-cpu cache
per-thread是传统模式,为每个线程都分配一个独立的cache
per-cpu是新模式,为每个cpu核分配一个独立的cache,线程获取内存时,先查看自己在哪个cpu核运行,再去对应的cpu-cache中申请核释放内存,它通过Restartable Sequences技术来实现线程切换后可以无锁的获取内存
small和large对象申请
small内存,tcmalloc通过slab数据结构维护,应用可以在O(1)的时间获取和释放内存
large内存,需要直接到back-end申请
伙伴系统
对于小于kMaxSize的对象,划分成以大小为单位的类别,每个类别的内存块使用链表连接
当需要申请k大小的内存时,在分类中找最相近大小的类,并获取链表中的内存块,并返回
Front-end cache的大小
cache的大小如果超过阈值,那么释放的内存会回收到Middle-End
cache的大小如果小于阈值,那么将向Middle-End申请一片内存
Middle-End
通过锁来保证middle-end的线程安全
Transfer cache
Transfer cache通过维护一个slab cache,提供给Front-end快速的申请和示范内存
Central Free List
以span为单位来维护内存
一个span一般由多个page组成
transfer的cache块是通过分裂span来建成的
cache块的meta data会记录span id,来标记它来自哪个span
span通过bitmap数据结果,来判定span内部哪些区域被分配出去,哪些部分已经回收到span
Pagemap
使用2-level或者3-level的radix tree来管理span
key是span的id
每个span下面管理着若干个page
Page size
tcmalloc对应的page size可以是4k、8k、32k和256k
小page更容易管理cache块,可以更快速的归还给OS,申请小page的overhead更小
大page更少的系统调用,tree会更小
Back-end
面向OS,向OS申请大块的内存和释放整块的内存
jemalloc介绍
jemalloc的设计跟tcmalloc是雷同的
所以这里只讲它跟tcmalloc在设计上做了哪些优化
arenas
jemalloc也是有per-thread cache
跟tcmalloc不一样的是
middle-end tcmalloc是全局只有一个
而jemalloc有多个组成,它们叫arenas
每个线程初始的时候绑定其中一个arena
多个arenas可以带来更小的全局锁竞争
slab数据结构的优化
tcmalloc的slab结构是以链表来维护的
它忽略了内存地址的因素
因此对于连续的申请内存,获取到的内存地址会十分随机
随着应用运行时间越长,会导致内部碎片越来越严重
应用的性能也会越来越差
jemalloc通过slab+rb-tree来维护内存块
每次提供给应用的内存块都是以最小内存地址的内存
这样,连续申请的内存,它们就会比较相邻
使得内存碎片得到缓和
网友评论