频繁分配释放内存的性能问题
在调优程序的过程中发现对于分辨率大小不同的两个图库,程序总的运行性能数据(如FPGA kernel利用率及每张图处理的latency)差别很大,使用/usr/bin/time –v分析程序发现测试两个图库时,报的minor falut(次缺页错误)不是一个数量级别的。
linux系统下面缺页异常主要分为四种情况。minor falut属于请求调页的一种。当malloc函数调用时,并未实际分配物理内存,而是仅仅分配了一段线性地址空间,在实际访问该页框时才会去分配实际的物理页框,这样可以节省物理内存的开销。
malloc分配内存,当分配内存小于128K的阈值时由brk系统调用完成;当大于此阈值时由mmap系统调用完成。brk系统调用是将mm_struct的brk字段不断往上增加,但是有一个RLIMIT_DATA大小的限制(这里不做分析);mmap是在进程的虚拟地址空间的文件映射区域找一块空闲的虚拟内存。
图1-进程地址空间布局与mm_struct结构图成员对应关系
由图1可以看到heap位于bss段的上面,它由低地址向高地址方向增长,它的起始地址由mm_struct结构体的start_brk标记,结束地址由brk标记。文件映射区由mmap_base标记基址,它是由高地址向地址的方向增长。
以下面的测试来看看malloc的工作情况:
1、申请100K bytes内存,ptr1=0x603010,此时进程heap空间start_brk=0x603000
此时heap内存布局如下:
申请100K的内存,实际分配大小为0x19010(100K+16B)。对于glibc malloc来说,用户请求分配的内存空间都用一个chunk(块)来表示,它是堆内存分配的基本单元,分为allocted chunk,free chunk,top chunk和last remainder chunk四种。
一个allocated chunk的内存布局如下:
如图所示Chunk指针指向一个chunk的开始,一个chunk包括user data(用户申请内存)和控制信息。
控制信息包括:
◆前一个chunk的size
◆此chunk的size
◆P(PREV_INUSE)位表示前一个chunk是否在使用中(0表示空闲,1表示正在使用),当前一个chunk正在使用中则prev size无效。第一个chunk的P 总是设置为1。
◆M(IS_MAPPED)位表示是从哪个内存区域获得的虚拟内存(1表示从mmap映射区域分配,0表示从heap分配的)
◆N(NON_MAIN_ARENA)位表示该chunk属于main arena还是thread arena(0表示main arena)。
多申请的16B的内存就是用来存储chunk header信息的。
allocated chunk大小为0x19010,加上top chunk的大小为0x20ff0,这次一共分配了232K
为什么申请了100K bytes内存,但是实际分配的vma大小为232K?
Glibc的内存分配中分为主分配区(main arena)和非主分配区(thread arena),每个进程只有一个主分配区,但可以有多个非主分配区。主分配区可以调用brk和mmap系统调用申请虚拟内存,非主分配区只能调用mmap系统调用申请虚拟内存。
Ptmalloc开始时会预先分配一块较大的空闲内存,这样可以减少后续的申请向操作系统申请的次数。主分配区的top chunk在第一次调用malloc时会分配一块(chunk size + 128KB) align 4KB大小的内存。所以这里就实际分配了(100KB + 128KB) align 4KB = 232KBytes的内存。
2、申请100K bytes内存,ptr2=0x61c020。
此时heap内存布局如下:
此次malloc申请100KB内存时,因为主arena区域第一次malloc时分配的空间大小为232KB,所以主arena在此时还剩余约132K大小,足够此次100KB的申请需求,因此直接分配。
3、申请100K bytes内存,ptr3=0x635030。
此时heap内存布局如下:
此次申请100K的内存,但是top chunk大小为0x7fe0(31K多),也就是top chunk不能满足分配需求。由于是在主arena,因此调用sbrk()函数,增加top chunk的大小。这里增加的大小为200KB
4、释放ptr1内存。
free释放的内存不会立马归还给系统,而是标记为free chunk,ptmalloc会统一管理free chunk,当下次请求分配内存时,会首先试图从free chunk中选择一个,这样就避免了频繁的系统调用。一个free chunk的布局如下:
Ptmalloc将相似大小的free chunk用双向链表链接在一起,这样的一个链表称为bin。Ptmalloc一共维护了128个bin,并使用一维数组来存储这些bin。根据chunk size大小的不同,bin分为以下几种:fast bin,unsorted bin,small bin和large bin。
◆fast bin
程序在运行过程中可能会频繁的申请和释放一些小内存,为了提高效率,ptmalloc引入了fast bin,小于等于max_fast(默认值64Byte)的allocated chunk被释放后,会放到fast bin中,fast bin中的chunk不改变P标志位,这样就不会将它们合并。当需要分配的内存小于等于max_fast时,会首先从fast bin中选择空闲块。
◆unsorted bin
unsorted bin是一维数组中第一个bin,如果用户释放的chunk大于max_fast,或者fast bin中的空闲chunk合并后,都是会放在unsorted bin中。在申请内存时,如果fast bin中没有找到合适的chunk,就会接着在unsorted bin中查找空闲的chunk。
◆small bin
一维数组中bit1~bit63的62个bin称为small bins,这些bin的chunk size小于512Byte。一个small bin中的chunk大小相同,两个相邻的bin的chunk大小相差8byte。第一个small bin的chunk大小为16byte,第二个small bin大小为24字节,以此类推最后一个small bin的chunk大小为504byte。
small bin中最后free的chunk被链接到链表的头部,则申请chunk则是从链表的尾部开始,因此链表中的chunk都有机会被ptmalloc选择分配。当free一个chunk的时候检查相邻的chunk是否是free chunk,如果是free的,就合并成一个更大的chunk,并将此chunk插入到unsorted bin链表的头部。
◆large bin
一维数组中small bins之后的就是large bin,这些bin的chunk size大于等于512Byte。
large bin中的chunk内存分配和释放比small bins中的要慢。large bins包含63个bin,每一个bin都是一个双链表,可以在large bin的任何位置(头部,尾部或者中间)添加或移除chunk。
63个large bin具体是如下情况:
Ⅰ)一维数组中bit64~bit95的32个bin,每个bin的chunk size大小都有64bytes的偏差范围,第一个large bin大小为512~568bytes,第二个large bin大小为568~632bytes,其余的以此类推。
Ⅱ)16个bin,其中每个bin双链表上的chunk大小偏差范围为512bytes。
Ⅲ)8个bin,其中每个bin双链表上的chunk大小偏差范围为4096bytes。
Ⅳ)4个bin,其中每个bin双链表上的chunk大小偏差范围为32768bytes。
Ⅴ)2个bin,其中每个bin双链表上的chunk大小偏差范围为262144bytes。
Ⅵ)1个bin,chunk大小为除上述大小外。
large bin双链表上的每个chunk大小可能是不同的,它们是按降序排列的,大的chunk排在链表的前面,小的chunk排在链表的后面。分配内存时从bin的链表的尾部往头部遍历,直到找到等于或者接近所请求的内存大小的chunk,此chunk会分割成两部分:一部分满足分配请求,chunk剩下的部分将加入到unsorted bin中。如果此bin的头部的chunk大小比所请求的内存都小,那就从相邻的下一个large bin中查找。如果遍历了所有的large bin都没有找到满足请求的chunk,那么就会使用top chunk满足分配请求。
根据上述的分析ptr1在free之后会放入unsorted bin中。从下图可以看到大小为0x19010的free chunk放在了unsorted bin中。
此时heap内存布局如下:
此free chunk不与top chunk相邻,且chunk size > max_fast,因此直接放入unsorted bin,不做其他,free直接返回。
5、释放ptr2内存。
ptr2释放后也会标记为free chunk,放入unsorted bin中,它会与之前的ptr1的free chunk合并,合并后的大小为0x19010+0x19010=0x32020。
此时heap内存布局如下:
此free chunk不与top chunk相邻,free直接返回。
6、释放ptr3内存。
ptr3释放后heap内存布局如下:
free chunk与top chunk相邻,则无论free chunk多大,都将与top chunk合并。合并之后的top chunk大小为0x66f000 - 0x603000 = 432KB大于FASTRBIN_CONSOLIDATION_THRESHOLD(默认64KB),则会触发进行fastbin的合并操作,遍历fastbin中的chunk,并与相邻的chunk合并,合并之后的chunk将会被放到unsorted bin。此时fastbin的情况如下:
fastbin中10个bin链表都未空,没有需要进行合并的。接下来看top chunk的大小是否大于mmap收缩阈值(默认128KB),此时是大于阈值的且位于main arena,因此会释放top chunk的一部分回操作系统。最先分配的128KB内存空间是不会释放的,ptmalloc会一直管理这片空间,用户满足后面的内存分配请求。
此时heap内存布局如下:
言归正传,回到频繁的malloc/free对系统性能的影响。这里有两个方面可以考虑:
◆少用或不使用mmap
mmap系统调用较之brk系统调用来说,首先在分配内存时mmap比brk耗时。其次mmap分配的映射区虚拟内存每次free都是调用munmap函数,会释放掉虚拟内存和物理内存,而brk分配的heap内存在free时则可以做到不是立即释放,这样可以重复使用,会减少minor falut。
Glibc提供有M_MMAP_MAX选项可以设置进程调用mmap分配的内存块的最大限制。设置为0,就不会使用mmap分配大块内存。
mallopt(M_MMAP_MAX, 0); |
◆重复使用空闲的chunk内存
在前面讲到malloc会从top chunk中分配内存,如果free之后free chunk大小加上前后能合并的chunk大于64KB,并且top chunk的大小达到收缩阈值,就会分配一部分内存给操作系统,可以设置M_TRIM_THRESHOLD的值为-1,关闭内存自动收缩,这样就可以重复使用。
mallopt(M_TRIM_THRESHOLD,-1); |
更多推荐
所有评论(0)