天天看点

开源内存池tcmalloc,jemalloc对比

lnmp一键安装包安装系统的时候一般用Jemalloc

TCMalloc 

优点:很多系统都可以用源来安装 TCMalloc ,而且支持的 gcc 编译库比较新。 

缺点:软件是在 Google Perftools 下的,安装的时候如果不编译好可能会安装到我们不需要的其他软件,而且 Google Perftools 安装过程比较复杂还需要安装相应的库。

Jemalloc 

优点:目前是 Maridab 、Tengine、Redis 中默认推荐的内存优化工具,所以使用 Jemalloc 对这些程序的兼容度还是比较高的。而且经过测试高负载情况下 Jemalloc 更加优秀。安装过程方便,不用安装额外的库。 

缺点:对使用最新的gcc编译不友好。

redis有用到jemalloc。

首先,jemalloc是干什么的? 

我们看看作者自己的介绍:

jemalloc is a general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support

意思是说jemalloc干了malloc干的活,而且干得好一些,主要体现在避免内存碎片与并发扩展上。

首先,什么是内存碎片?

 malloc/free或new/delete大量使用后回造成内存碎片,那么这种碎片形成的机理是什么?  

如果机理是申请的内存空间大小(太小)所形成的,那么,申请多大的区域能够最大限度的避免内存碎片呢?(这里的避免不是绝对的避免,只是一种概率)

内存碎片一般是由于空闲的连续空间比要申请的空间小,导致这些小内存块不能被利用。产生内存碎片的方法很简单,举个例:  

假设有一块一共有100个单位的连续空闲内存空间,范围是0~99。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为0~9区间。这时候你继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为10~14区间。  

如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。  

现在整个内存空间的状态是0~9空闲,10~14被占用,15~24被占用,25~99空闲。其中0~9就是一个内存碎片了。如果10~14一直被占用,而以后申请的空间都大于10个单位,那么0~9就永远用不上了,造成内存浪费。  

如果你每次申请内存的大小,都比前一次释放的内村大小要小,那么就申请就总能成功。 

内存碎片很可怕吗? 

短时间内不明显,对于线上的服务器比如nginx,运行时间可能经年,每天几亿的请求量,如果每天产生100片 ,一年下来也是3万多片 ,相当恐怖!

jemalloc可以有效的解决这个问题! 

使用jemalloc或tcmalloc可以有效提升mysql的性能,强烈建议大家可以尝试。

简化后的步骤如下:

1. yum -y install autoconf make

2. 安装 jemalloc 

 wget http://www.canonware.com/download/jemalloc/jemalloc-3.6.0.tar.bz2

 tar -xjf jemalloc-3.6.0.tar.bz2

 cd jemalloc-3.6.0

 ./configure --prefix=/usr/local/jemalloc --libdir=/usr/local/lib

 make && make install

3.my.cnf中添加配置,并重启mysql(mysql 5.5+)

[mysqld_safe]

malloc-lib=/usr/local/lib/libjemalloc.so

4.验证是否生效

lsof -n | grep jemalloc

cmalloc

tcmalloc是Google开源的一个内存管理库, 作为glibc malloc的替代品。目前已经在chrome、safari等知名软件中运用。

根据官方测试报告,ptmalloc在一台2.8GHz的P4机器上(对于小对象)执行一次malloc及free大约需要300纳秒。而TCMalloc的版本同样的操作大约只需要50纳秒。

小对象分配

tcmalloc为每个线程分配了一个线程本地ThreadCache,小内存从ThreadCache分配,此外还有个中央堆(CentralCache),ThreadCache不够用的时候,会从CentralCache中获取空间放到ThreadCache中。

小对象(<=32K)从ThreadCache分配,大对象从CentralCache分配。大对象分配的空间都是4k页面对齐的,多个pages也能切割成多个小对象划分到ThreadCache中。

tcmalloc小对象类型链表

小对象有将近170个不同的大小分类(class),每个class有个该大小内存块的FreeList单链表,分配的时候先找到best fit的class,然后无锁的获取该链表首元素返回。如果链表中无空间了,则到CentralCache中划分几个页面并切割成该class的大小,放入链表中。

CentralCache分配管理

大对象(>32K)先4k对齐后,从CentralCache中分配。 CentralCache维护的PageHeap如下图所示, 数组中第256个元素是所有大于255个页面都挂到该链表中。

tcmalloc-pageheap

当best fit的页面链表中没有空闲空间时,则一直往更大的页面空间则,如果所有256个链表遍历后依然没有成功分配。 则使用sbrk, mmap, /dev/mem从系统中分配。

tcmalloc PageHeap管理的连续的页面被称为span.

如果span未分配, 则span是PageHeap中的一个链表元素

如果span已经分配,它可能是返回给应用程序的大对象, 或者已经被切割成多小对象,该小对象的size-class会被记录在span中

在32位系统中,使用一个中央数组(central array)映射了页面和span对应关系, 数组索引号是页面号,数组元素是页面所在的span。 在64位系统中,使用一个3-level radix tree记录了该映射关系。

回收

当一个object free的时候,会根据地址对齐计算所在的页面号,然后通过central array找到对应的span。

如果是小对象,span会告诉我们他的size class,然后把该对象插入当前线程的ThreadCache中。如果此时ThreadCache超过一个预算的值(默认2MB),则会使用垃圾回收机制把未使用的object从ThreadCache移动到CentralCache的central free lists中。

如果是大对象,span会告诉我们对象锁在的页面号范围。 假设这个范围是[p,q], 先查找页面p-1和q+1所在的span,如果这些临近的span也是free的,则合并到[p,q]所在的span, 然后把这个span回收到PageHeap中。

CentralCache的central free lists类似ThreadCache的FreeList,不过它增加了一级结构,先根据size-class关联到spans的集合, 然后是对应span的object链表。如果span的链表中所有object已经free, 则span回收到PageHeap中。

tcmalloc的改进

ThreadCache会阶段性的回收内存到CentralCache里。 解决了ptmalloc2中arena之间不能迁移的问题。

Tcmalloc占用更少的额外空间。例如,分配N个8字节对象可能要使用大约8N * 1.01字节的空间。即,多用百分之一的空间。Ptmalloc2使用最少8字节描述一个chunk。

更快。小对象几乎无锁, >32KB的对象从CentralCache中分配使用自旋锁。 并且>32KB对象都是页面对齐分配,多线程的时候应尽量避免频繁分配,否则也会造成自旋锁的竞争和页面对齐造成的浪费。

性能对比

官方测试

测试环境是2.4GHz dual Xeon,开启超线程,redhat9,glibc-2.3.2, 每个线程测试100万个操作。

尤其是对于小内存的分配, tcmalloc有非常明显性能优势。

随着线程数的增加,tcmalloc性能上也有明显的优势,并且相对平稳。

github mysql优化

github使用tcmalloc后,mysql性能提升30%

Jemalloc

jemalloc是facebook推出的, 最早的时候是freebsd的libc malloc实现。 目前在firefox、facebook服务器各种组件中大量使用。

jemalloc原理

与tcmalloc类似,每个线程同样在<32KB的时候无锁使用线程本地cache。

Jemalloc在64bits系统上使用下面的size-class分类:

Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]

Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]

Huge: [4 MiB, 8 MiB, 12 MiB, …]

small/large对象查找metadata需要常量时间, huge对象通过全局红黑树在对数时间内查找。

虚拟内存被逻辑上分割成chunks(默认是4MB,1024个4k页),应用线程通过round-robin算法在第一次malloc的时候分配arena, 每个arena都是相互独立的,维护自己的chunks, chunk切割pages到small/large对象。free()的内存总是返回到所属的arena中,而不管是哪个线程调用free()。

可以看到每个arena管理的arena chunk结构, 开始的header主要是维护了一个page map(1024个页面关联的对象状态), header下方就是它的页面空间。 Small对象被分到一起, metadata信息存放在起始位置。 large chunk相互独立,它的metadata信息存放在chunk header map中。

通过arena分配的时候需要对arena bin(每个small size-class一个,细粒度)加锁,或arena本身加锁。

并且线程cache对象也会通过垃圾回收指数退让算法返回到arena中。

jemalloc Arena and thread cache layout

jemalloc的优化

Jmalloc小对象也根据size-class,但是它使用了低地址优先的策略,来降低内存碎片化。

Jemalloc大概需要2%的额外开销。(tcmalloc 1%, ptmalloc最少8B)

Jemalloc和tcmalloc类似的线程本地缓存,避免锁的竞争

相对未使用的页面,优先使用dirty page,提升缓存命中。

4.3.2 mysql优化

测试环境:2x Intel E5/2.2Ghz with 8 real cores per socket,16 real cores, 开启hyper-threading, 总共32个vcpu。 16个table,每个5M row。

OLTP_RO测试包含5个select查询:select_ranges, select_order_ranges, select_distinct_ranges, select_sum_ranges,

在多核心或者多线程的场景下, jemalloc和tcmalloc带来的tps增加非常明显。

参考资料

glibc内存管理ptmalloc源代码分析

Inside jemalloc

tcmalloc浅析

tcmalloc官方文档

Scalable memory allocation using jemalloc

mysql-performance-impact-of-memory-allocators-part-2

ptmalloc,tcmalloc和jemalloc内存分配策略研究

Tick Tock, malloc Needs a Clock