开源内存池之Tcmalloc介绍
开源内存池之Tcmalloc介绍
一、使用原理
1、 通过“void *__libc_malloc(size_t size) __attribute__ ((weak, alias(”tcmalloc”))); ” 的方式替换程序中调用malloc(glibc)的操作,在tcmalloc中重新实现内存分配策略,调用sbrk 或者memmap进行内存分配。
2、 thread cache,对每个线程,维护一些内存,线程申请小内存时,就直接在thread cache中获取,这样避免了多线程的锁操作带来的开销。同时,由于一开始就申请了一大段内存作为thread cache,所以不用在每次申请内存时都在内核态和用户态之间切换。
3、 每个小对象的大小都会被映射到170个可分配的尺寸类别中的一个。一个线程缓存(thread cache)对每个尺寸类都包含了一个自由对象的单向链表。
4、 central heap,对于一个进程,预申请一个大的内存空间,如果线程申请的内存过大或者thread cache已经不够用了,就在central heap中获取内存,多线程的情况下需要加锁解锁,不过相对于glibc的malloc,预申请内存减少了很多内核态和用户态之间切换。
二、 为什么要用内存管理器
1、 malloc 和 new 函数是通用的内存分配器。您的代码可能是单线程的,但它所链接的 malloc 函数同样可以处理多线程的范例。正是由于这个额外功能,使得这些例程的性能有所降低。(为单线程程序优化内存管理)
2、 在执行时,malloc 和 new 将向操作系统内核请求内存,而 free 和 delete 则请求释放内存。这意味着,操作系统必须在每次提出内存请求时在用户态和核心态之间进行切换。反复调用 malloc 或者 new 的程序,最终将由于不断地进行上下文切换而导致运行缓慢。
3、 对于在程序中分配的、并且以后不再需要使用的内存,常常会忘记对其进行删除,并且 C/C++ 并没有提供自动的垃圾收集。这将导致程序的内存空间占用进一步增长。在实际的大型程序中,性能将受到显著的影响,因为可用内存变得越来越少,并且硬盘访问是非常耗时的。
三、如何评估一个内存管理器的好坏
1、速度
与编译器提供的分配器相比,内存管理器的速度必须更快一些。重复的分配和释放不应该降低代码的执行速度。如果可能的话,应该对内存管理器进行优化,以便处理代码中频繁出现的某些分配模式。
2、垃圾回收,内存泄露
在程序终止之前,内存管理器必须归还它向系统请求的所有内存。也就是说,不应该存在内存泄漏。它还应该能够处理错误的情况(例如,请求过大的内存),并且很好地解决这些问题。
3、用户使用便利性
在将内存管理器集成到他们的代码中时,用户应该只需要更改很少的代码。
4、可移植性
应该可以很容易地将内存管理器移植到其它的系统,并且不应该使用与平台相关的内存管理特性。
5、 减少内存碎片。
内存分配策略应该能适应普遍的内存管理需求,而不是特定的程序,减少内存碎片。
6、 灵活通用性
某些内存管理器为了提高速度,处理代码中频繁出现的某些分配模式,定制的内存管理器不灵活,一旦需求改变,代码中的出现跟多的分配模式,反而可能造成内存管理器速度不如malloc的情况。
四、 如果我们使用这个内存管理器,要怎么使用
1、 编译时,将tcmalloc库链接到程序中,静态libtcmalloc.a或者动态libtcmalloc.so。
2、 不重新编译,运行之前,设置LD_PRELOAD=”libtcmalloc.so”。(不推荐)
五、 tcmalloc与malloc性能的比较
比较方法,在每个线程中运行1百万次malloc和free操作,计算出cpu每秒能处理多少百万次内存申请释放操作。
下图中,横坐标是每一次malloc时最大的size(随机生成要申请的内存大小),分别为64,128,256,1K,2K,…,128K;纵坐标是一秒能处理的操作数(单位是百万)。
图一是在我的笔记本上的测试结果,tcmalloc的效率远高于malloc,在线程数达到20的时候,tcmalloc的效率有明显的降低。
图一
下面这张图比较大,可以看清楚横纵坐标。图片较多,就不一一列出。
图二是在开发机(10.10.196.154)上的测试结果,可以看出tcmalloc的效率一直是接近malloc的两倍。
图二
测试所用代码如下: