子线程中的 malloc 占用太多虚拟内存

malloc in child thread cost too much virtual memory

void * thread_client_timeout_check(void *arg)
{
    pthread_attr_t attr;size_t size;
    pthread_attr_init(&attr);
    pthread_attr_getstacksize(&attr, &size);
    printf("pthread stacksize: %d\n", size);
    malloc(1);
}

主线程创建子线程并暂停。

int main()
{
    pthread_t pid;
    pthread_create(&pid, NULL, thread_client_timeout_check, NULL);
    pause();
}
  1. pthread_create之前,top virt0.3m
  2. pthread_create之后,top virt8.3m(pthread栈大小为8m)
  3. malloc(1)之后,top virt72.3m

为什么malloc(1)会从内核获取54m虚拟内存?

在多线程程序中,glibc 2.10+ 创建了多个 malloc 池以减少错误共享,从而提高可扩展性。结果是从 glibc 2.10 开始,虚拟内存使用率会高很多。但是由于地址 space 很便宜,或者在 64 位架构上或多或少是免费的,所以真的没什么好担心的。

https://udrepper.livejournal.com/20948.html

虚拟内存(virt)不是程序分配的内存。类似于一种内存足迹(包含数据+代码+驻留+交换内存。它还包含共享库使用的共享代码和数据段)。 Glibc 的 malloc 内存分配针对不同的块大小(fastbins、mmap()、sbrk())使用不同的策略,较小的实际内存使用量会导致巨大的 virt 内存。例如。分配 10 次 64 KiB + 1KiB 块并释放较低的 10 x 64KiB 块。实际使用 1 KiB,但在 virt 中计算的堆上内存为 641 KiB(+cca 100 KiB 用于最顶层(所谓的 wilderness)块),因为堆上释放的内存仍然属于进程的地址 space .

您可以使用 mallinfo(3):

查看实际可用内存
#include <malloc.h>

#define pfld(fld, unit, rem) printf("  %-9s= %d %s, (%s)\n", #fld, mi.fld, #unit, rem)

void showmem(const char *fnc, const char *step) {
    struct mallinfo mi = mallinfo();

    printf("\n==== %s: %s ====\n", fnc, step);

    pfld(arena,    bytes, "Non-mmapped space allocated");
    pfld(ordblks,  pcs,   "free chunks");
    pfld(smblks,   pcs,   "free fastbin blocks");
    pfld(hblks,    pcs,   "mmapped regions");
    pfld(hblkhd,   bytes, "Space allocated in mmapped regions");
    pfld(usmblks,  bytes, "Maximum total allocated space");
    pfld(fsmblks,  bytes, "Space in freed fastbin blocks");
    pfld(uordblks, bytes, "Total allocated space");
    pfld(fordblks, bytes, "Total free space");
    pfld(keepcost, bytes, "Top-most, releasable space");
}

从程序的不同部分调用此函数,如 showmem(__FUNCTION__, "Step"),您可以看到分配的总量 space 和空闲的总量 space。我假设在您的情况下,总免费 space 很高,而总分配 space 很低。这可能是因为 pthread 库分配和释放的内存。

你可以做个测试。使用 mallopt(3) 你可以要求 malloc 总是使用 mmap(2) 来分配内存而不是更方便的 sbrk(2)。通常 mmap(2) 仅用于大于或等于 128 KiB 的块。当内核提供 malloc 时,mmap 的内存总是清零(导致处理开销并总是分配 4KiB 页面),并且在释放之后总是从进程的地址 space 中删除并返回给内核(因此从空闲中读取'd 指针将导致分段错误)。如果释放了 mmap 的内存,它会立即从您的地址 space 中删除,因此分配的内存占用空间将立即减少。尝试:malloc 10 x 128 KiB + 1 KiB,然后释放 10 x 128 KiB。 Virt 将缩减为仅包含 1 KiB 部分。

呼叫

mallopt(M_MMAP_THRESHOLD, 0);

在你程序的开头会强制glibc的malloc总是使用mmap(3)。如果我的假设是正确的,那么 virt mem 将会减少。我不建议使用这个(对于小块它会导致很大的内存开销并且总是用 0 填充内存页导致 CPU 开销),但理论可以测试。

这仅适用于 glibc。其他内存管理器(如 tcmalloc)使用不同的策略。

希望对您有所帮助!