现在的位置: 首页 > 综合 > 正文

tclmalloc内存管理

2013年09月26日 ⁄ 综合 ⁄ 共 1862字 ⁄ 字号 评论关闭

 

这是一个通用的内存管理库,可以代替new delete之类。内存管理主要关注两点,一是分配、释放的速度,二是内存的利用率,也就是内存碎片问题。这两个目标是冲突的,不同的内存管理算法在两者之间取不同的平衡点

为了提高分配、释放的速度,多核计算机上,主要做的工作是避免所有核同时在竞争内存,常用的做法是内存池,简单来说就是批量申请内存,然后切割成各种长度,各种长度都有一个拉链,申请、释放都只要在链表上操作,可以认为是O(1)的。

不可能所有的长度都对应一个链表。很多内存池是假设,A释放掉一块内存后,B会申请类似大小的内存,但是A释放的内存跟B需要的内存不一定完全相等,可能有一个小的误差,如果严格按大小分配,会导致复用率很低,这样各个拉链上都会有很多释放了,但是没有复用的内存,导致利用率很低。这个问题也是可以解决的,可以回收这些空闲的内存,但是这样就变成传统的内存管理,不停地对内存块作切割和合并,会导致效率低下。所以通常的做法是只分配有限种类的长度。一般的内存池只提供几十种选择。tcmalloc在这点做得比较激进,对于小内存,按8的整数次倍分配,对于大内存,按4K的整数次倍分配。这样做有两个好处,一是分配的时候比较快,那种提供几十种选择的内存池,往往要遍历一遍各种长度,才能选出合适的种类,而tcmalloc则可以简单地做几个运算就行了。二是短期的收益比较大,分配的小内存至多浪费7个字节,大内存则4K。但是长远来说,tcmalloc分配的种类还是比别的内存池要多很多的,可能会导致复用率很低。

所以tcmalloc还要有一套高效的机制回收这些空闲的内存。当一个线程的空闲内存比较多的时候,会交还给进程,进程可以把它调配给其他线程使用;如果某种长度交还给进程后,其他线程并没有需求,进程则把这些长度合并成内存页,然后切割成其他长度。如果进程占据的资源比较多呢,据说不会交回给操作系统。

相当于常见的内存池,tcmalloc的优势体现在:

(1)分配内存页的时候,直接跟OS打交道,而常用的内存池一般是基于别的内存管理器上分配,如果完全一样的内存管理策略,明显tcmalloc在性能及内存利用率上要省掉第三方内存管理的开销。之所以会出现这种情况,是因为大部分写内存池的coder都不太了解OS

(2)大部分的内存池只负责分配,不管回收。当然了,没有回收策略,也有别的方法解决问题。比如线程之间协调资源,模索模块一般是一写多读,也就是只有一个线程申请、释放内存,就不存在线程之间协调资源;为了避免某些块大量空闲,常用的做法是减少内存块的种类,提高复用率,这可能会造成内部碎片比较多,如果空闲的内存实在太多了,还可以直接重启。

作为一个通用的内存管理库,tcmalloc也未必能超过专用的比较粗糙的内存池。比如应用中主要用到7种长度的块,专用的内存池,可以只分配这7种长度,使得没有内部碎片。或者利用统计信息设置内存池的长度,也可以使得内部碎片比较少,以前做过一个工作是统计一下历史上的需求,然后用动态规则去算内存池长度设置,可以使得内部碎片很少,长度分布发生改变,则重启。

所以tcmalloc的意义在于,不需要增加任何开发代价,就能使得内存的开销比较少,而且可以从理论上证明,最优的分配不会比tcmalloc的分配好很多。

 

对比glibc可以发现,两者的思想其实是差不多的,差别只是在细节上,细节上的差别,对工程项目来说也是很重要的,至少在性能与内存使用率上tcmalloc是领先很多的。glibc在内存回收方面做得不太好,常见的一个问题,申请很多内存,然后又释放,只是有一小块没释放,这时候glibc就必须要等待这一小块也释放了,也把整个大块释放,极端情况下,可能会造成几G的浪费

代码风格方面,tcmalloc是用c风格写的,个人比较喜欢这种风格。不像有些人,喜欢摆弄C++的语法,先声明一个虚基类,然后反复继承实例化之类,一点小功能写个几百上千行,看得云里雾里。当然tcmalloc的代码也很长,我还没读完

 

 

但是,tclmalloc进行内存管理,不能用valgrind检查内存情况。可以使用google-perftools提供的heap checker
使用方法:
export  HEAPCHECK=TYPE
TYPE可以为:minimal、normal、strict、draconian

更细节可以参考:http://google-perftools.googlecode.com/svn/trunk/doc/tcmalloc.html

抱歉!评论已关闭.