dlmalloc

 

流行分配器dlmalloc剖析之:设计理念

版权声明: 本文章由vt.buxiu发布在www.vtzone.org,版权归vtzone研究小组所有,转载请保持此声明!!! 

@@内容摘要:doug lea malloc的设计理念,大部分内容来自doug lea本人的dlmalloc说明@@
局部性改善依赖于局部性原理:应用同一时刻请求的内存块,倾向于有相同的用途,并倾向于具有相同的生命周期。具有好的局部性,将可以减少内存页面切换,提高处理器的缓存命中率,这对应用可能产生很大的性能改善。

如果单考虑局部性改善,分配器可以每次都分配距离上次分配最近距离的内存块满足应用,这就是所谓的nearest-fits(与best-fits)类 似,但这种策略会导致严重的碎片。因此,在dlmalloc实现时,在于其他目标冲突最小的时候使用了next-fits:只有当请求的size对应的 chunk不存在,并且最近一次分割出来的chunk正好满足或大于这次请求的大小的时候,将上次分割出来的chunk用来满足这次请求(如果还是大于这 次请求,则进行分割);否则还是使用best-fits进行查找bins。由于next-fits比best-fits要快,这种方法改善了 dlmalloc的平均分配时间,并且这种严格限制条件的使用next-fits,改善了这次请求正好与上次分割出来size完全匹配没被马上使用的情 况。
Wilderness内存块Wilderness chunk指其边界是进程heap最高点的那个chunk,由于wilderness可以通过brk(Unix/Linux)进行任意的扩展(除非系统内 存耗尽brk失败)或缩小。如果把wilderness和其他chunk一样对待,则可能其他chunk还可以满足请求的时候就使用了wilderness,这就增加了可能调用brk向系统获得更多内存的机会,造成更严重的内存消耗。
Dlmalloc
对wilderness chunk特殊对待,将其作为一个bigger chunk,作为best-fits的扩展,只有当其他chunk不能满足的请求的时候才用wilderness满足应用请求。进一步避免了严重内存消耗。
内存映射除 了通过调用brk系统调用接口扩展进程的heap大小之外,大多数Unix操作系统都提供了mmap系统调用,为应用分配一块不连续的内存区域(这里指多 次连续调用mmap获得内存之间的不连续,一次调用mmap获得内存是连续的)。Mmap为malloc中满足应用内存请求提供了除brk之外的第二种选 择。
dlmalloc
使用系统调用接口 brk和mmap向底层操作系统管理heap空间。Brk系统调用可以改变进程当前heap最高点的指针,使其向上或向下移动(即扩大或减小heap大 小).而使用mmap满足非常大的内存块请求,mmap总是以页面位单位进行内存映射,即使只mmap一个byte也会映射一整个页面,大部分系统页面大 小是4096bytes,有些系统可能更大MiB or 2MiB with PAE on IA-32)。但mmap的优点是一旦释放后能够立刻被操作系统分配给其他进程使用。所以glibc中的doug lea实现的分配器(以下简称dlmalloc)结合使用mmap满足大内存块的请求,brk管理heap满足小内存块请求。

Mmap
的使用可以进一步减少内存碎片的产生,因为mmap并不会产生需要管理的holes,但由于mmap的实现和其内部bookkeeping消耗限 制mmap只能在有限的场合下才适合使用,比如:在当前所有的提供mmap的系统中,mmap都必须按页面对齐,也就是说mmap调用将比分配存在的 chunk要慢得多,并且造成更多的内部碎片。

因此,dlmalloc仅仅在两种情况下使用mmap,1。对于应用请求大于指定阀值(默认1MB)时,使用mmap满足应用请求;2。内部chunk无法满足应用需要,并且heap空间通过brk无法扩展的时候,使用mmap。

由于有些操作系统当前并未提供mmap,dlmalloc还针对wilderness chunk超过特定阀值之后,将过多的没有用的wilderness内存返回给操作系统。
caching分割和合并chunk需要时间,有时候分割合并的操作可以避免,有两种优化手段:
缓式合并:

当应用free一个chunk时,分配器并不立即对其与其他相邻的空闲chunk进行合并操作,而是期望不久的将来应用会再次请求同样大小的chunk一边快速满足其请求。
预分配:

不是每次应用请求一个size的chunk才进行分配,而是预先分配多个chunk,这样等下次应用请求该大小的chunk时候就不需要分配操作,直接返回已有的空闲chunk以便满足请求。
对于需要连续大量分配或者释放一定范围 内大小的应用程序,进行caching是很有意义的,而如果应用请求和释放的内存块的大小分布很广泛,没什么规律,进行caching将没什么意义,相反 会造成严重的内存消耗。由于分配器对应用的实用内存规律无从知晓,所以很难确定何时进行分配,是应该将小块合并满足大块请求,还是保留小块以便将来满足同 样大小的请求。老版本的dlmalloc对应用使用内存规律作出了某些假设,实际应用结果表明,caching并没有改善dlmalloc的性能,比如在 C++程序中,可能存在大量的类,而每个class的大小并不相等从而造成了大量范围的size,引起最坏情况的发生导致dlmalloc性能下降。最新 的dlmalloc只针对小块内存才进行caching。
小对象分配 由于dlmalloc默认按8bytes对齐,并且最小分配的内存块大小16bytes,对于那些大量分配指针(4bytes)的应用,将造成无法忍受的内存消耗。关于这个话题,mayers在他的effective C++,以及alex在他的modern C++ Design中有过专门的介绍。

分配大小限制由于dlmalloc内部管理内存块称之为chunk,分配大小的限制依据于chunk的定义。Chunk结构如下:

struct malloc_chunk {


INTERNAL_SIZE_T
prev_size;
/* Size of previous chunk (if free).
*/


INTERNAL_SIZE_T
size;
/* Size in bytes, including overhead. */

  struct malloc_chunk* fd;
/* double links -- used only if free. */

  struct malloc_chunk* bk;

};
由上面讨论所知,INTERNAL_SIZE_T类型记录的就是维护chunk大小信息的类型,默认情况下被定义为size_t,另外即使在64-bit系统上也可以将此类型定义为32-bit从而介绍块头的空间消耗。
最大可分配内存:
最大可以分配的内存块大小依赖于物理内存和操作系统,理论上可以分配size_t(unsigned int)类型能够表示的大小的内存,因此理论上最大值是(size_t)-1,或者由C99标准规定的一个对象能够分配的最大内存大小SIZE_MAX(0x7FFF in C90, 0xFFFF in C99)
虽然C标准规定了size_tunsigned int,但有些系统没有遵守将其定义为signed int;另外系统调用brk有的参数也是定义为signed int;在这些系统上将无法满足范围到(size_t)-1的内存块请求。但mmap()不内存signed int的限制问题。
最小可分配内存:
dlmalloc最小可分配内存有以下宏值定义:

/* The smallest possible chunk */

#define MIN_CHUNK_SIZE
(
sizeof(struct malloc_chunk))

#define MINSIZE
\


(
unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

由上面定义可以看出最小可分配内存同样和INTERNAL_SIZE_T类型有关系,另外也和指针大小、对齐方式有关系。
       对于指针大小为4bytes,默认情况下头占用4bytes(INTERNAL_SIZE_T定义为size_t)MINSIZE将等于16。下表列出了几种典型情况,其他情况下的MINSIZE读者可以自行计算。

指针大小

INTERNAL_SIZE_T大小

对齐方式

MINSIZE

4bytes

4bytes

8bytes

16bytes

8bytes

4bytes

8bytes

24bytes

4bytes

8bytes

8bytes

32bytes

 

 

 

 

即使对于malloc(0)dlmalloc也会返回一块MINSIZE的内存给用户。

因为管理内存内存块需要,最大消耗的内存小于等于MINSIZE。但当请求大小超过mmap_thresholddlmalloc将使用mmap满足请求,这种情况下浪费的最大空间是2*sizeof(size_t)+mmap为了按页(40968192bytes)对齐需要的空间。

对齐方式dlmalloc中对齐相关的代码如下:

#ifndef MALLOC_ALIGNMENT

#define MALLOC_ALIGNMENT
(2 * SIZE_SZ)

#endif

/* The corresponding bit mask value */

#define MALLOC_ALIGN_MASK
(MALLOC_ALIGNMENT - 1)

size_t4字节时,dlmalloc默认的对齐方式为8bytes对齐,这默认的对齐方式基本能够使用所有的系统和C编译器,如果有需要可以通过定义MALLOC_ALIGNMENT指定更大宽度的对齐方式,必须为2的次幂。


作者:vt.buxiu@www.vtzone.org


dlmalloc是目前一个十分流行的内存分配器,其由Doug Lea(主页为http://gee.cs.oswego.edu/)从1987年开始编写,到目前为止,最新版本为2.8.3(可以从ftp://g.oswego.edu/pub/misc/malloc.c获取),由于其高效率等特点被广泛的使用(比如一些linux系统等用的就是dlmalloc或其变形,比如ptmalloc,主页为http://www.malloc.de/en/index.html)和研究(各位可以搜索关键字“GCspy”)。 dlmalloc的实现只有一个源文件(还有一个头文件),大概5000行,其内注释占了大量篇幅,由于有这么多注释存在的情况下,表面上看上去很容易懂,的确如此,在不追求细节的情况,对其大致思想的确很容易了解(没错,就只是了解而已),但是dlmalloc作为一个高品质的佳作,实现上使用了非常多的技巧,在实现细节上不花费一定的精力是没有办法深入理解其为什么这么做,这么做的好处在哪,只有当真正读懂后回味起来才发现它是如此美妙。 lenky0401个人博客将陆续推出对dlmalloc的解析(针对Doug Lea Malloc的最新版Version 2.8.3,未做说明的情况下以32位平台,8字节对齐作为假定平台环境设置考虑),由于个人水平有限,因此也不能完全保证对dlmalloc的所有理解都准备无误, 但是所有内容均出自个人的理解而并非存心妄自揣测来愚人耳目,所以如果读者发现其中有什么错误,请勿见怪,如果可以则请来信告之,并欢迎来信讨论(lenky0401@163.com)。 这一系列文章是lenky0401在看完dlmalloc的大部分代码后的再总结,不能保证对dlmalloc的整体完全把握,贴出这些只是希望可以提前收到对此有研究的网友的指点,以便在最后对这一系列文章整理而形成的PDF文档中错误能少一些。至于对于现在贴出来的内容中包含的错误给大家造成的不便提前说声抱歉。:) 描述的内容不会包含dlmalloc全部代码,但会将这其中涉及到的一些技巧尽量讲出,我相信对dlmalloc源代码不感兴趣的朋友也可以学到这些独立的技巧而使用在自己的编程实践中。:) 最后,转载请保留本博客地址连接[http://lenky0401.cublog.cn],谢谢。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值