关于glibc的malloc内存对齐
来源:互联网 发布:sql server 自然连接 编辑:程序博客网 时间:2024/05/16 17:54
http://blog.csdn.net/elpmis/article/details/4500917
最近需要写一个程序,这程序对内存比较敏感,如果采用最简单的分配策略至少要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中,有如下宏定义:
- #ifndef INTERNAL_SIZE_T
- #define INTERNAL_SIZE_T size_t
- #endif
- #define SIZE_SZ (sizeof(INTERNAL_SIZE_T))
- #ifndef MALLOC_ALIGNMENT
- #define MALLOC_ALIGNMENT (2 * SIZE_SZ)
- #endif
其中MALLOC_ALIGNMENT这个宏就是对齐的单位,在32位系统中size_t一般4个字节,在64位系统中一般8个字节,那样在32位和64位的对齐单位分别为8字节和16字节。那是不是最小分配内存单位也是MALLOC_ALIGNMENT的大小呢?这里就要牵涉到glibc分配内存的管理机制。在glibc中空闲的内存块会有双链表连起来,每个链表结点就是一个管理单元,它的结构定义如下:
- 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;
- };
- An allocated chunk looks like this:
- chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of previous chunk, if allocated | |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of chunk, in bytes |M|P|
- mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | User data starts here... .
- . .
- . (malloc_usable_size() bytes) .
- . |
- nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Size of chunk |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
当一块内存分配出去后,该内存块的信息所占用内存也会随着分配出去,但用户是用不到这些内存的,如图中如示 mem-> 后面才是用户可用内存的起点。到这里,或许你已经想到,使用malloc时消耗系统内存的最小单位就是sizeof(struct malloc_chunk)。实际上,差不多就是这样,只是还需要将sizeof(struct malloc_chunk)作内存对齐。
malloc.c中有如下代码:
- #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
- #define MIN_CHUNK_SIZE (sizeof(struct malloc_chunk))
- #define MINSIZE /
- (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
- /* pad request bytes into a usable size -- internal version */
- #define request2size(req) /
- (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? /
- MINSIZE : /
- ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
其中request2size这个宏就是glibc的内存对齐操作,MINSIZE就是使用malloc时占用内存的最小单位。根据宏定义可推算在32位系统中MINSIZE为16字节,在64位系统中MINSIZE一般为32字节。从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统可用内存少了32字节,当申请内存为25字节时,系统内存实际少了48字节。
为了证实这个事实,我在64位linux上运行以下程序:
- //test.cpp
- //g++ -Wall -o test test.cpp
- #include <vector>
- using namespace std;
- int main(int argc, char *argv[])
- {
- int malloc_size = atoi(argv[1]);
- vector<char *> malloc_vec(1 * 1024 * 1024);
- for (size_t i = 0; i < malloc_vec.size(); ++i) {
- malloc_vec[i] = new char[malloc_size];
- }
- while (1) {}
- return 0;
- }
分别运行“./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的动机和我差不多,呵呵。
- 关于glibc的malloc内存对齐
- 关于glibc的malloc内存对齐
- 内存对齐,malloc内存对齐
- glibc malloc 内存管理 分析
- 内存对齐的malloc、realloc、free
- 关于Malloc字节对齐的思考
- 关于glibc中内存回收的试验
- Glibc 的malloc源代码分析
- Glibc 的malloc源代码分析
- glibc对malloc的实现
- Glibc 的 malloc 源代码分析
- 浅析基于glibc的malloc
- Glibc 中malloc的实现
- glibc下malloc的理解
- 关于内存对齐的探讨
- 关于内存对齐的理解
- 关于内存对齐的总结
- 关于内存对齐的理解
- spring工作机制及为什么要用
- Twitter利用Storm系统处理实时大数据
- 转码——native2ascii.exe的使用方法
- wifi display
- asp.net 方法与技巧的总结
- 关于glibc的malloc内存对齐
- 利用WebBrowser控件创建自己的浏览器
- Redis 数据库的键值设计
- 后PC时代较劲苹果安卓 Windows RT深入评测
- 23种设计模式之命令模式3
- Objective-C风暴来袭,精彩不容错过
- SQL 日常检查脚本
- C#设置输入法
- VC,webbrowser控件中弹出新网页窗口