在调优程序的过程中发现对于分辨率大小不同的两个图库,程序总的运行性能数据(如FPGA kernel利用率及每张图处理的latency)差别很大,使用/usr/bin/time  –v分析程序发现测试两个图库时,报的minor falut(次缺页错误)不是一个数量级别的。

  linux系统下面缺页异常主要分为四种情况。minor falut属于请求调页的一种。当malloc函数调用时,并未实际分配物理内存,而是仅仅分配了一段线性地址空间,在实际访问该页框时才会去分配实际的物理页框,这样可以节省物理内存的开销。

  malloc分配内存,当分配内存小于128K的阈值时由brk系统调用完成;当大于此阈值时由mmap系统调用完成。brk系统调用是将mm_structbrk字段不断往上增加,但是有一个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的内存,实际分配大小为0x19010100K+16B)。对于glibc malloc来说,用户请求分配的内存空间都用一个chunk(块)来表示,它是堆内存分配的基本单元,分为allocted chunkfree chunktop chunklast remainder chunk四种。

  一个allocated chunk的内存布局如下:


  如图所示Chunk指针指向一个chunk的开始,一个chunk包括user data(用户申请内存)和控制信息。

控制信息包括:

◆前一个chunksize

◆此chunksize

P(PREV_INUSE)位表示前一个chunk是否在使用中(0表示空闲,1表示正在使用),当前一个chunk正在使用中则prev size无效。第一个chunkP 总是设置为1

M(IS_MAPPED)位表示是从哪个内存区域获得的虚拟内存(1表示从mmap映射区域分配,0表示从heap分配的)

N(NON_MAIN_ARENA)位表示该chunk属于main arena还是thread arena0表示main arena)。


多申请的16B的内存就是用来存储chunk header信息的。


allocated chunk大小为0x19010,加上top chunk的大小为0x20ff0,这次一共分配了232K

为什么申请了100K bytes内存,但是实际分配的vma大小为232K?

    Glibc的内存分配中分为主分配区(main arena)和非主分配区(thread arena),每个进程只有一个主分配区,但可以有多个非主分配区。主分配区可以调用brkmmap系统调用申请虚拟内存,非主分配区只能调用mmap系统调用申请虚拟内存。

    Ptmalloc开始时会预先分配一块较大的空闲内存,这样可以减少后续的申请向操作系统申请的次数。主分配区的top chunk在第一次调用malloc时会分配一块(chunk size + 128KBalign 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大小为0x7fe031K多),也就是top chunk不能满足分配需求。由于是在arena,因此调用sbrk()函数,增加top chunk的大小。这里增加的大小为200KB


4、释放ptr1内存。

       free释放的内存不会立马归还给系统,而是标记为free chunkptmalloc会统一管理free chunk,当下次请求分配内存时,会首先试图从free chunk中选择一个,这样就避免了频繁的系统调用。一个free chunk的布局如下:


       Ptmalloc将相似大小的free chunk用双向链表链接在一起,这样的一个链表称为binPtmalloc一共维护了128bin,并使用一维数组来存储这些bin。根据chunk size大小的不同,bin分为以下几种:fast binunsorted binsmall binlarge 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~bit6362bin称为small bins,这些binchunk size小于512Byte。一个small bin中的chunk大小相同,两个相邻的binchunk大小相差8byte。第一个small binchunk大小为16byte,第二个small bin大小为24字节,以此类推最后一个small binchunk大小为504byte

      small bin最后freechunk被链接到链表的头部,则申请chunk则是从链表的尾部开始,因此链表中的chunk都有机会被ptmalloc选择分配。当free一个chunk的时候检查相邻的chunk是否是free chunk,如果是free的,就合并成一个更大的chunk,并将此chunk插入到unsorted bin链表的头部。

large bin

  一维数组中small bins之后的就是large bin,这些binchunk size大于等于512Byte

      large bin中的chunk内存分配和释放比small bins中的要慢。large bins包含63bin,每一个bin都是一个双链表,可以在large bin的任何位置(头部,尾部或者中间)添加或移除chunk

      63large bin具体是如下情况:

)一维数组中bit64~bit9532bin,每个binchunk size大小都有64bytes的偏差范围,第一个large bin大小为512~568bytes,第二个large bin大小为568~632bytes,其余的以此类推。

)16bin,其中每个bin双链表上的chunk大小偏差范围为512bytes

)8bin,其中每个bin双链表上的chunk大小偏差范围为4096bytes

)4bin,其中每个bin双链表上的chunk大小偏差范围为32768bytes

)2bin,其中每个bin双链表上的chunk大小偏差范围为262144bytes

)1binchunk大小为除上述大小外。

      large bin双链表上的每个chunk大小可能是不同的,它们是按降序排列的,大的chunk排在链表的前面,小的chunk排在链表的后面。分配内存时从bin的链表的尾部往头部遍历,直到找到等于或者接近所请求的内存大小的chunk,此chunk会分割成两部分:一部分满足分配请求,chunk剩下的部分将加入到unsorted bin中。如果此bin的头部的chunk大小比所请求的内存都小,那就从相邻的下一个large bin中查找。如果遍历了所有的large bin都没有找到满足请求的chunk,那么就会使用top chunk满足分配请求。


  根据上述的分析ptr1free之后会放入unsorted bin中。从下图可以看到大小为0x19010free chunk放在了unsorted bin中。

此时heap内存布局如下:

  此free chunk不与top chunk相邻,且chunk size > max_fast,因此直接放入unsorted bin,不做其他,free直接返回。


5、释放ptr2内存。


      ptr2释放后也会标记为free chunk,放入unsorted bin中,它会与之前的ptr1free 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的情况如下:

      fastbin10bin链表都未空,没有需要进行合并的。接下来看top chunk的大小是否大于mmap收缩阈值(默认128KB)此时是大于阈值的且位于main arena,因此会释放top chunk的一部分回操作系统。最先分配的128KB内存空间是不会释放的,ptmalloc会一直管理这片空间,用户满足后面的内存分配请求。

  此时heap内存布局如下:




言归正传,回到频繁的malloc/free对系统性能的影响。这里有两个方面可以考虑:

少用或不使用mmap

      mmap系统调用较之brk系统调用来说,首先在分配内存时mmapbrk耗时。其次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); 

 


GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐