Malloc使用的内存是所需内存的10倍

Joh*_*y V 4 c memory memory-management

我有一个网络应用程序,它将可预测的65k块分配为IO子系统的一部分。内存使用情况是在系统内自动跟踪的,因此我知道我实际使用了多少内存。也可以根据malloc_stats()检查此数字

的结果 malloc_stats()

Arena 0:
system bytes     =    1617920
in use bytes     =    1007840
Arena 1:
system bytes     = 2391826432
in use bytes     =  247265696
Arena 2:
system bytes     = 2696175616
in use bytes     =  279997648
Arena 3:
system bytes     =    6180864
in use bytes     =    6113920
Arena 4:
system bytes     =   16199680
in use bytes     =     699552
Arena 5:
system bytes     =   22151168
in use bytes     =     899440
Arena 6:
system bytes     =    8765440
in use bytes     =     910736
Arena 7:
system bytes     =   16445440
in use bytes     =   11785872
Total (incl. mmap):
system bytes     =  935473152
in use bytes     =  619758592
max mmap regions =         32
max mmap bytes   =   72957952
Run Code Online (Sandbox Code Playgroud)

注意事项:

  • total in use bytes根据我的内部计数器,这是完全正确的数字。但是,该应用程序的RES(从顶部/顶部)为5.2GB。分配几乎总是65k;我不明白在mmap发挥作用时,我看到的更多碎片/废料甚至更多。
  • total system bytes不等于system bytes每个竞技场的总和。
  • 我在使用glibc 2.23-0ubuntu3的Ubuntu 16.04上
  • Arena 1和Arena 2导致内核报告的RES值很大。
  • 1号和2号竞技场的存储量保持在原来的10倍。
  • 大部分分配始终为65k(页面大小的显式倍数)

如何保留malloc来分配大量的内存?

我认为此版本的malloc有一个巨大的错误。最终(一个小时后)将释放一半以上的内存。这不是致命的错误,但绝对是一个问题。

更新-我添加mallinfo并重新运行了测试-捕获该应用时,该应用不再处理任何内容。没有连接网络。它是空闲的。

Arena 2:
system bytes     = 2548473856
in use bytes     =    3088112
Arena 3:
system bytes     = 3288600576
in use bytes     =    6706544
Arena 4:
system bytes     =   16183296
in use bytes     =     914672
Arena 5:
system bytes     =   24027136
in use bytes     =     911760
Arena 6:
system bytes     =   15110144
in use bytes     =     643168
Arena 7:
system bytes     =   16621568
in use bytes     =   11968016
Total (incl. mmap):
system bytes     = 1688858624
in use bytes     =   98154448
max mmap regions =         32
max mmap bytes   =   73338880
arena (total amount of memory allocated other than mmap)                 = 1617780736
ordblks (number of ordinary non-fastbin free blocks)                     =       1854
smblks (number of fastbin free blocks)                                   =         21
hblks (number of blocks currently allocated using mmap)                  =         31
hblkhd (number of bytes in blocks currently allocated using mmap)        =   71077888
usmblks (highwater mark for allocated space)                             =          0
fsmblks (total number of bytes in fastbin free blocks)                   =       1280
uordblks (total number of bytes used by in-use allocations)              =   27076560
fordblks (total number of bytes in free blocks)                          = 1590704176
keepcost (total amount of releaseable free space at the top of the heap) =     439216
Run Code Online (Sandbox Code Playgroud)

我的假设如下:total system bytes报告的之间的差异malloc远小于所报告的差异arena。(1.6Gb vs 6.1GB)这可能意味着(A)malloc实际上正在释放块,但竞技场却没有,或者(B)malloc根本没有压缩内存分配,并且正在创建大量的碎片。

UPDATE Ubuntu发布了一个内核更新,该更新基本上修复了本文中所述的所有内容。就是说,这里有很多关于malloc如何与内核一起工作的好信息。

Cra*_*tey 5

完整的细节可能会有些复杂,因此我将尽量简化。另外,这是一个粗略的轮廓,在某些地方可能会有些不准确。


从内核请求内存

malloc使用sbrk或匿名使用来自内核mmap连续内存区域。每个区域将是机器页面大小的倍数,通常为4096字节。这样的存储区域在术语称为竞技场malloc。下面的更多内容。

这样映射的任何页面都将成为进程的虚拟地址空间的一部分。但是,即使已将它们映射到,它们也可能尚未由物理 RAM页面备份。在R / O模式下,它们被[多对一]映射到单个“零”页面。

当进程尝试写入此类页面时,会引发保护错误,内核会中断到零页面的映射,分配实际的物理页面,然后重新映射到该页面,然后在故障点重新启动该过程。这次写入成功。这类似于到/从分页盘的按需分页。

换句话说,进程的虚拟地址空间中的页面映射与物理RAM页面/插槽中的页面驻留不同。稍后再详细介绍。


RSS(居民集大小)

RSS并不能真正衡量一个进程分配或释放多少内存,而是目前其虚拟地址空间中有多少页在RAM中具有物理页。

如果系统具有128GB的分页磁盘,但仅具有(例如)4GB的RAM,则过程RSS永远不能超过4GB。进程的RSS根据其虚拟地址空间中的页面调入或调出页面而上升/下降。

因此,由于启动时页面映射为零,因此进程RSS可能比其从系统请求的虚拟内存量低得多。同样,如果另一个进程B从某个给定的进程A“窃取”一个页面槽,则A的RSS下降,B的上升。

进程“工作集”是内核必须为进程保留的最小页数,以基于某种“过分”的措施来防止进程因页面错误而过度获取物理内存页。每个操作系统对此都有自己的想法,并且通常是整个系统或每个进程的可调参数。

如果一个进程分配了一个3GB的阵列,但只访问它的前10MB,则它的工作集要比随机/分散访问该阵列的所有部分时要低。

也就是说,如果RSS高于(或可以高于)工作集,则该过程将运行良好。如果RSS低于工作集,则该过程将过度出现页面错误。这可能是因为它的“参考位置”较差,也可能是因为系统中的其他事件合谋“窃取”了该进程的页面位置。


malloc和竞技场

为了减少碎片,请malloc使用多个舞台。每个竞技场都有一个“首选”分配大小(又称“大块”大小)。也就是说,较小的请求malloc(32)来自(例如)竞技场A,但较大的请求malloc(1024 * 1024)来自(例如)竞技场B。

这样可以防止小分配“烧录”竞技场B中最后一个可用块的前32个字节,从而使其太短而无法满足下一个 malloc(1M)

当然,我们不能为每个请求的大小提供单独的区域,因此“首选”块大小通常为2的幂。

当为给定的块大小创建一个新的竞技场时,malloc不仅要请求块大小的区域,还需要它的某个倍数。这样做是为了可以快速满足相同大小的后续请求,而不必mmap为每个请求都做一个。由于最小大小为4096,因此竞技场A将具有4096/32个块或128个块。


免费和munmap

当应用程序执行一个free(ptr)[ ptr代表一个块]时,该块将被标记为可用。free可以选择连续块是自由/当时可用或合并不是

如果块足够小,它什么都不做更多(IE)的块是可用于重新分配,但是,free不会尝试释放块回内核。对于较大的分配,free将[尝试] munmap立即执行。

munmap即使位于多页区域的中间,也可以取消映射单个页面[甚至是很少的字节数]。如果是这样,则应用程序现在在映射中有一个“洞”。


malloc_trim和madvise

如果free被调用,则可能调用munmap。如果未映射整个页面,则该过程的RSS(例如A)关闭。

但是,请考虑仍分配的块,或标记为可用/可用但未映射的块。

它们仍然是流程A的RSS的一部分。如果另一个进程(例如B)开始进行大量分配,则系统可能必须将某些进程A的插槽分页到分页盘上(减少A的RSS),以便为B腾出空间(其RSS上升)。

但是,如果没有进程B窃取A的页面槽,则进程A的RSS可以保持较高水平。假设进程A分配了100MB,前一阵子用了,但是现在只使用1MB,RSS仍为100MB。

那是因为没有进程B的“干扰”,内核没有理由从A窃取任何页面槽,因此它们“保留在RSS中”。

要告诉内核不太可能很快使用内存区域,我们需要使用madvisesyscall MADV_WONTNEED。这告诉内核,内存区域的优先级较低,它应[更多]积极地将其分页到分页磁盘,从而减少进程的RSS。

页面映射在进程的虚拟地址空间中,但被导出到分页磁盘。请记住,页面映射不同比页面居住

如果该进程再次访问该页面,则将导致页面错误,内核将把数据从页面磁盘拉到物理RAM插槽并重新映射。RSS回去。古典需求分页。

madvisemalloc_trim用来减少过程的RSS的东西。

  • @MaximEgorushkin 这是“glibc”源代码[我在写答案时一直在看]。如果该块已进行 mmap,则“free”立即调用“munmap”。它只对“sbrk”分配的块进行更传统的块组合/分割 (2认同)