Malloc使用10倍的内存量

时间:2016-09-28 16:44:28

标签: 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

需要注意的事项:

  • 根据我的内部计数器,total in use bytes是完全正确的数字。但是,该应用程序具有5.2GB的RES(来自top / htop)。分配几乎总是65k;当mmap发挥作用时,我不太了解我所看到的大量碎片/浪费。
  • total system bytes不等于每个竞技场中system bytes的总和。
  • 我使用glibc 2.23-0ubuntu3在Ubuntu 16.04上
  • 竞技场1和2说明了内核报告的大RES值。
  • 竞技场1和2保持使用的内存量的10倍。
  • 大多数分配总是65k(页面大小的明确倍数)

如何保留malloc以分配荒谬的内存量?

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

更新 - 我添加了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

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

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

2 个答案:

答案 0 :(得分:4)

完整的细节可能有点复杂,所以我会尽可能地简化事情。此外,这是一个粗略的轮廓,可能在某些地方稍微不准确。

从内核请求内存

malloc使用sbrk或匿名mmap从内核请求连续的内存区域。每个区域将是机器页面大小的倍数,通常为4096字节。这样的存储区域在malloc术语中称为 arena 。更多关于以下内容。

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

当进程尝试写入此类页面时,会发生保护错误,内核会将映射分解为零页面,分配实际物理页面,重新映射到该页面,并在故障点重新启动进程。这次写入成功。这类似于向/从寻呼盘请求寻呼。

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

RSS(常驻集大小)

RSS并不是衡量一个进程分配或释放多少内存的指标,但它的虚拟地址空间中有多少页目前在RAM中有一个物理页面。

如果系统的寻呼磁盘为128GB,但只有(例如)4GB的RAM,则进程RSS永远不会超过4GB。该进程的RSS基于其虚拟地址空间中的页面分页或分页而上/下。

因此,由于启动时的零页面映射,进程RSS可能远低于它从系统请求的虚拟内存量。另外,如果另一个进程B"窃取"来自给定进程A的页面槽,A的RSS下降并上升为B.

流程"工作集"是内核必须为进程保留的最小页数,以防止进程过度页面错误以获取物理内存页面,基于某种程度的过度"。每个操作系统都有自己的想法,它通常是系统范围或每个进程的可调参数。

如果一个进程分配一个3GB的数组,但只访问它的前10MB,那么它的工作集将低于随机/散射访问数组的所有部分的工作集。

也就是说,如果RSS比工作集更高[或可以更高],则该过程将运行良好。如果RSS低于工作集,则该过程将过度分页。这可能是因为它的参考位置很差"或者因为系统中的其他事件合谋“窃取”#34;进程的页面插槽。

malloc and arenas

为了减少碎片,malloc使用了多个竞技场。每个竞技场都有一个'#34;首选"分配大小(又名" chunk"大小)。也就是说,像malloc(32)这样的较小请求来自(例如)竞技场A,但像malloc(1024 * 1024)这样的较大请求来自不同的竞技场(例如)竞技场B.

这可以防止"燃烧"竞技场B中最后一个可用块的前32个字节,使其太短而无法满足下一个malloc(1M)

当然,我们不能为每个要求的尺寸设置单独的竞技场,因此"首选"块大小通常是2的幂。

当为给定的块大小创建新的竞技场时,malloc不仅仅请求块大小的区域,而是请求其中的一些区域。它这样做可以快速满足相同大小的后续请求,而无需为每个请求执行mmap。由于最小尺寸为4096,竞技场A将有4096/32个块或128个块。

免费和munmap

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

如果块足够小,它不会再做任何事情(即)块可用于重新分配,但是,free 尝试将块释放回内核。对于较大的分配,free将[尝试]立即执行munmap

munmap可以取消映射单个页面[甚至是少量字节],即使它位于多页长度的区域中间。如果是这样,应用程序现在有一个"孔"在映射中。

malloc_trim和madvise

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

但是,请考虑仍然分配的块,或者标记为空闲/可用但未取消映射的块。

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

但是,如果有没有进程B窃取A页面的插槽,则进程A的RSS可以保持很高。假设进程A分配了100MB,暂时使用它,但现在只使用1MB,RSS将保持在100MB。

那是因为没有"干扰"从进程B开始,内核有 no 的理由从A中窃取任何页面槽,因此它们会保留在书籍上。在RSS中。

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

页面保留映射到进程的虚拟地址空间,但是会被转移到分页磁盘。请记住,页面映射 不同而不是页面驻留

如果进程再次访问该页面,则会发生页面错误,内核会将数据从分页磁盘提取到物理RAM插槽并重新映射。 RSS重新开始。经典的需求传呼。

madvisemalloc_trim用于减少流程RSS的内容。

答案 1 :(得分:2)

free不承诺将释放的内存返回给操作系统。

您观察到的是释放的内存保留在进程中以便可能重用。更重要的是,malloc_trim向操作系统释放内存可能会在大块的分配和释放频繁发生时造成性能问题。这就是为什么有一个选项可以使用malloc_trim(0)明确地将内存返回给操作系统。

尝试keepcost,看看是否会降低RSS。此函数是非标准函数,因此其行为是特定于实现的,它可能根本不执行任何操作。你在评论中提到调用它确实减少了RSS。

在开始深入挖掘之前,您可能希望确保没有内存泄漏和内存损坏。

关于man mallinfo成员,请参阅{{1}}:

  

<强> BUGS

     

仅为主内存分配区域返回信息。          其他竞技场的分配将被排除在外。请参阅malloc_stats(3)和malloc_info(3)以了解包含其他竞技场信息的替代方案。