理解glibc malloc修剪

7

我正在开发的某个程序所消耗的内存比我预想的要多得多。因此,我正在尝试了解glibc malloc修剪的工作原理。我编写了以下测试:

#include <malloc.h>
#include <unistd.h>

#define NUM_CHUNKS 1000000
#define CHUNCK_SIZE 100

int main()
{
    // disable fast bins
    mallopt(M_MXFAST, 0);

    void** array  = (void**)malloc(sizeof(void*) * NUM_CHUNKS);

    // allocating memory
    for(unsigned int i = 0; i < NUM_CHUNKS; i++)
    {
        array[i] = malloc(CHUNCK_SIZE);
    }

    // releasing memory ALMOST all memory
    for(unsigned int i = 0; i < NUM_CHUNKS - 1 ; i++)
    {
        free(array[i]);
    }

    // when enabled memory consumption reduces
    //int ret = malloc_trim(0);
    //printf("ret=%d\n", ret);

    malloc_stats();

    sleep(100000);
}

测试输出(未调用malloc_trim):

Arena 0:
system bytes     =  112054272
in use bytes     =        112
Total (incl. mmap):
system bytes     =  120057856
in use bytes     =    8003696
max mmap regions =          1
max mmap bytes   =    8003584

尽管几乎所有的内存都被释放了,但这个测试代码消耗的驻留内存比预期的多得多。
[root@node0-b3]# ps aux | grep test
root     14662  1.8  0.4 129736 **118024** pts/10  S    20:19   0:00 ./test

进程内存映射:

0245e000-08f3b000 rw-p 00000000 00:00 0                                  [heap]
Size:             109428 kB
Rss:              109376 kB
Pss:              109376 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:    109376 kB
Referenced:       109376 kB
Anonymous:        109376 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac 
7f1c60720000-7f1c60ec2000 rw-p 00000000 00:00 0 
Size:               7816 kB
Rss:                7816 kB
Pss:                7816 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:      7816 kB
Referenced:         7816 kB
Anonymous:          7816 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB

当我启用malloc_trim的调用时,测试的输出几乎保持不变:
ret=1
Arena 0:
system bytes     =  112001024
in use bytes     =        112
Total (incl. mmap):
system bytes     =  120004608
in use bytes     =    8003696
max mmap regions =          1
max mmap bytes   =    8003584

然而,RSS显著降低:
[root@node0-b3]# ps aux | grep test
root     15733  0.6  0.0 129688  **8804** pts/10   S    20:20   0:00 ./test

malloc_trim之后的进程smaps:

01698000-08168000 rw-p 00000000 00:00 0                                  [heap]
Size:             109376 kB
Rss:                   8 kB
Pss:                   8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         8 kB
Referenced:            8 kB
Anonymous:             8 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac 
7f508122a000-7f50819cc000 rw-p 00000000 00:00 0 
Size:               7816 kB
Rss:                7816 kB
Pss:                7816 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:      7816 kB
Referenced:         7816 kB
Anonymous:          7816 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB

调用malloc_trim后,堆被压缩了。我认为由于未释放的最后一块内存,8MB映射段仍然可用。
为什么malloc不会自动执行堆修剪? 是否有一种方法可以配置malloc以便在可以节省那么多内存时自动执行修剪?
我正在使用glibc版本2.17。

如果你正在使用大量内存并需要以特定的方式处理它,我建议在POSIX上使用mmap,在Windows上使用VirtualAlloc自己处理。 - Zan Lynx
1个回答

7
出于历史原因,小内存分配的内存来自使用brk系统调用管理的池中。这是一个非常古老的系统调用 —— 至少与Version 6 Unix一样古老 —— 它唯一能做的就是更改一个在内存中位置固定的“区域”的大小。这意味着,brk池不能缩小到仍然分配的块之下。
您的程序分配了N个内存块,然后释放了其中N-1个。它不释放的那个块位于最高地址。这是brk的最坏情况:尽管99.99%的池未被使用,但大小根本无法减小!如果您更改程序,使其不释放的块为array[0]而不是array[NUM_CHUNKS-1],则应在最后调用free时看到RSS和地址空间都会缩小。
当您显式调用malloc_trim时,它会尝试使用Linux扩展madvise(MADV_DONTNEED)解决这个限制,该扩展释放物理RAM,但不释放地址空间(正如您所观察到的)。我不知道为什么只有在显式调用malloc_trim时才会发生这种情况。
顺便说一下,8MB mmap段是为您的array的初始分配而设定的。

谢谢你的回答! 我看到array确实驻留在8MB段上,我也看到array[N-1]驻留在堆段的末尾。如果它在末尾,我现在不明白在调用malloc_trim时它如何被修剪。 - michael
我明白了,谢谢。除了不时调用malloc_trim之外,有没有办法减少RSS内存消耗的数量? - michael
1
我会尝试的第一件事是插入 jemalloc - zwol
更改malloc实现对我来说不是一个选项。 - michael
1
这基本上是glibc的malloc的限制,所以如果你从malloptmalloc_trim得到的控制不够好,替换实现基本上是你唯一的选择。我在这里向glibc开发人员提到了这个限制:https://sourceware.org/ml/libc-alpha/2016-07/msg00646.html 但这可能不会很快帮助你,特别是如果你被困在2.17(当他们即将发布2.24)中。 - zwol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接