2013年1月30日星期三

glibc内存管理ptmalloc2瓶颈分析

之前遇到一个glibc-2.4内存管理的性能问题,这里拿来分析一下。

问题现象是业务程序响应很慢,cpu占用升高。最初怀疑是程序代码逻辑的问题,为确认这一点,首先使用oprofile进行采样分析。

oprofile可以以进程、动态库、内核模块、内核为分析对象,统计cpu具体消耗在什么地方。

使用oprofile进行抓取后,看到并不是上层程序问题,而是glibc在消耗cpu,安装对应glibc版本的debuginfo包之后,看到具体消耗cpu的是以下这一段代码:















为什么以上一段while语句消耗大量cpu?结合上层程序代码和glibc中ptmalloc2管理内存的机制,有以下分析。

首先ptmalloc2是以以下方式管理内存的:

















其中chunk表示一块内存,small bins存储16~512 bytes的内存块链表,相邻的bin之间相差8bytes,large bins存储大于512bytes的不规则大小内存块链表,unsorted bin存放调用free/delete进行内存释放后,未归入small bins和large bins的内存块。

small bins下各链表的内存块大小固定,如以上16下面连接的内存块均为16bytes;而large bins下各链表的内存块大小在一个范围内,如576下面连接的内存块可以为 [576, 640) 大小的内存块,并且从大到小排好序。

调用free/delete之后,内存并不是直接还给操作系统,而归还由ptmalloc2管理。对于属于large bins的free chunk,将放入适合的bin,并且每一个large bin下形成由大到小的有序链表。

以下结构用于表示一个free chunk:







接下来调用malloc/new申请一个大于512bytes的内存块时,将采用best-fit的方式,通过fd指针遍历large bins下某一条链表,查找方法正如以上代码所示。假设有N块内存,那么搜寻best-fit内存块的时间复杂度就是O(N),N值很大时,这将使得后续的malloc/new大于512bytes的调用非常慢。


为缓解这个问题,glibc-2.6释出一个patch,在表示内存块的chunk结构中,除了原连接chunk的双向链表指针fd和bk,增加了fd_nextsize和bk_nextsize指针,用于将size相同的chunk组成双向链表:









当在large bins中查找某大小的chunk时,通过fd_nextsiz遍历链表,而不是fd,这样就跳过了相同大小的chunk,减少无谓遍历:






当然问题并没有真正解决,以上方法在有相当多同样size的chunk时奏效,有大量size不等的大于512bytes的chunk时,同样会有malloc调用返回慢问题。

另struct mallinfo和mallinfo调用可用于获取malloc信息,帮助我们分析ptmalloc2相关问题,其中struct mallinfo中的ordblks字段表示空闲内存块的数量,可以理解为以上的N值。

参考:

复现问题的代码:sizetest.cpp
glibc-2.6中针对该问题的patch:serious performance regression in glibc-2.5 malloc


Have fun!

没有评论:

发表评论