关于glibc的malloc内存对齐

来源:互联网 发布:中国家具卖场的数据 编辑:程序博客网 时间:2024/05/17 04:28

    最近需要写一个程序,这程序对内存比较敏感,如果采用最简单的分配策略至少要16G,而我们的服务器一般也就16G物理内存。因此需要采取各种策略减少内存的使用。后来采用的方法是一开始只分配少量内存,后面需要的时候再分配。再次分配的时候需要copy旧的数据到新的内存块中,然后把旧内存块释放掉。为了减少copy的次数,新内存块是旧内存块的2倍,也就是指数增长。这种设计类似于C++中vector的内存分配策略。为什么不直接用vector呢?因为vector本身需要一定的空间,而我的程序有很多的内存分配目标(256M个),如果每一个用一个vector来管理的话,那也会占用很多内存,在64位Linux中占24Bytes,在32位Linux中占12Bytes。而如果自己管理内存的话,每个内存分配目标用一个指针,在64位Linux中只占8Bytes,在32位Linux中只占4Bytes。
    按照这种设计方法,我估计程序在64位Linux中占用内存应该在8G到10G左右,但是跑了很多次,最后都因为内存不足(大于16G)出现了Bus error。为什么会比想像中多占用这么多内存呢? 最开始的想法就是内存泄漏,但检查了很多遍程序,又用valgrind查看了好多遍,都没有发现有内存泄漏。后来想是不是申请内存的时候系统会分配额外的内存。因为我用的是glibc,然后就打算看一下glibc中的malloc是如何实现的,虽然我用C++中的new来申请内存,但new底层其实也还是调用malloc。
    我从GNU网站中把glibc2.5整个down了下来,然后看其中的malloc.c文件。从malloc.c文件中看到了一个memory alignment的概念,中文译过来可以说是内存对齐,它与一般的字节对齐意思不同,这里对齐是指在动态分配内存的时候,最终的分配的内存是某个2次幂的倍数。因此,分配内存会有一个最小分配单元,不管你申请多少,都不会少于某个值。在malloc.c中,有如下宏定义:

    其中MALLOC_ALIGNMENT这个宏就是对齐的单位,在32位系统中size_t一般4个字节,在64位系统中一般8个字节,那样在32位和64位的对齐单位分别为8字节和16字节。那是不是最小分配内存单位也是MALLOC_ALIGNMENT的大小呢?这里就要牵涉到glibc分配内存的管理机制。在glibc中空闲的内存块会有双链表连起来,每个链表结点就是一个管理单元,它的结构定义如下:

    当一块内存分配出去后,该内存块的信息所占用内存也会随着分配出去,但用户是用不到这些内存的,如图中如示 mem-> 后面才是用户可用内存的起点。到这里,或许你已经想到,使用malloc时消耗系统内存的最小单位就是sizeof(struct malloc_chunk)。实际上,差不多就是这样,只是还需要将sizeof(struct malloc_chunk)作内存对齐。
malloc.c中有如下代码:

    其中request2size这个宏就是glibc的内存对齐操作,MINSIZE就是使用malloc时占用内存的最小单位。根据宏定义可推算在32位系统中MINSIZE为16字节,在64位系统中MINSIZE一般为32字节。从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统可用内存少了32字节,当申请内存为25字节时,系统内存实际少了48字节。
    为了证实这个事实,我在64位linux上运行以下程序:

分别运行“./test 1","./test 24“和“./test 25",运行后查看程序占用内存可知,前两者占用内存为40M,后者占用56M,这就验证了前面的推断。首先,malloc_vec会占用8*1M,如果./test的输入参数<=24,程序在for循环中增加了32M内存,每次增加32字节,加起来刚好40M;如果./test的输入参数为25,在for循环中增加了48M内存,每次增加48字节,加起来56M。
    上述程序在32位系统中的结果如何,你可以自己试一下?
    现在,我终于明白了为什么之前的程序会多占用那么多内存。我可以针对内存对齐对程序进行优化。一开始我想,那将程序编译成32位目标代码(g++的-m32参数)不就可以节省很多不必要的内存了吗?不过试了后才想起32位程序的硬伤-4GB memory limit,这4G内存用完了还是不够用。后来我只能尽量使得申请内存的大小尽量靠近24,40,56...这些点(发生阶跃的点),这样可以获得尽量多的可用内存,优化后的程序基本上可以满足需求。
    关于gblic的malloc实现还有很多其它的内容,自己也只是看了一小部分, 关于malloc分配策略大家可以看这篇(http://blog.chinaunix.net/u3/94916/showart_1908306.html),那个作者去看malloc.c的动机和我差不多,呵呵。