【手写数据库内核组件】0301 动态内存池,频仍malloc/free让系统不堪重负, ...

打印 上一主题 下一主题

主题 907|帖子 907|积分 2721

动态内存管理

   ​专栏内容
  

  • postgresql使用入门基础
  • 手写数据库toadb
  • 并发编程
    个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以发奋图强;地势坤,君子以厚德载物.
  
  
一、概述


在平时运行时分配内存时,使用动态内存分配malloc/free或 new/delete,会直接从操作系统申请虚拟内存,释放时也归还给操作系统;
而操作系统会记录那些内存片断正在使用,那些已经释放;时间久后,内存碎片就越来越多,最终没法分配一片较大的内存出来,以是系统会只管让空闲的片断连成一整片,不停的整理。
一样平常应用程序需要动态分配的次数和频率不高时,对操作系统的负担不重;
而像数据库这样的重型应用跑起来,会有大量的运行时动态内存的分配和释放,如果不对动态内存举行用户态的管理,必然会拖慢操作系统的运行,影响全部应用的性能。
本文就来分享一种在应用程序中举举措态内存管理的方法,淘汰向操作系统申请和释放的次数,来避免内存碎片整理的负担。
二、动态内存管理原理


动态内存管理的目标重要有两个:


  • 淘汰向操作系统申请和释放的次数,也就是淘汰调用malloc/free;
  • 淘汰内存碎片,一旦产生碎片就需整理,同时会造成一部分内存的浪费;
2.1 内存分配策略

针对这两个目标,我们可以这样来做,先从内存的分配来看:
   

  • 每次从操作系统申请一块较大的内存;单次数目多,次数就会少很多;
  • 应用需要时,从这块大内存中切割划分,直到大块内存用尽,再重复1步调;
  • 对于少量一次需要很大的内存时,超过每次申请的上限,此时就直接从操作系统申请,这就按特例来处理。
  这有点像买蛋糕,当一个人吃时,每次就买一小块;当过生日时,有很多人一起吃,那就一次性买个大蛋糕,返来切分。
我们也可以说算法来源于生活,而高于生活!
2.2 内存接纳策略

对于内存的释放接纳,有两种策略:


  • 重复使用策略,释放时不归还给操作系统;下次申请时再重复使用;
  • 整体归还策略, 释放时不归还操作系统,如果一大块都释放时,则将此整块内存统一归还给操作系统;
重复使用策略
这种策略对于内存使用次数较高,与操作系统交互更少,但需要增长内存碎片的管理,也就是对于应用使用的不同巨细的内存片,归还后要么举行移动合并,要么按巨细举行排序再使用;
会增长碎片管理的负担,同时内存会有一些浪费。
整体归还策略
申请一大块内存,在应用内部举行切分为更小的片举行使用,当此大块内存上的分片均释放时,将此大块内存归还给操作系统。
这样避免内存碎片的整理,同时大块的释放也会淘汰操作系统内存碎片整理的负担;
但是也会存在内存浪费,当大块内存上有一小片一直不释放时,整块内存就会被浪费,迟迟得不到释放。
当然两种策略还可以再优化,但过多的优化,都会带来一些额外的开销。
三、动态内存管理实现


动态内存管理的基本单位称为内存页(page),巨细为4KB,也就是上节提到的大块内存,每次申请与释放都会按内存页来操作。



  • 而对于应用程序来讲,它申请内存是从memPage中举行分配,每次分配一个memNode,包含申请的内存巨细。
  • 当一个MemPage用完或不够时,再从下一个memPage中分配。
  • 全部的memPage接纳单链表的形式串起来,方便释放时管理。
下面我们就分拆来了解一下,内存页的定义,以及内存的申请与释放的操作。
3.1 内存管理结构定义

内存管理需要记录mempage的链表,同时为了更少的与操作系统交互,增长了一个freeList来记录释放的memPage,当freeList不空时,直接从这里分配即可。
内存管理结构的定义
  1. #define MEMORY_POOL_MANAGER_VERSION (0x0B10)
  2. typedef struct MemPoolManagerContext
  3. {
  4.     int             version;
  5.     unsigned long   totalSize;    /* 已经使用的动态内存大小 */
  6.     // SPINLOCK        lock;         /* protected this structure. */
  7.    
  8.     DList           memFreeList;   
  9.     DList           memPageList;      /* memPageInfo list */
  10.     MemPageInfo     *currUsePage;
  11. }MemPoolManagerContext;
复制代码
成员阐明:


  • version,模块的版本;
  • totalSize,记录当前使用了多少真实的内存空间;
  • memFreeList,空闲内存页的链表;
  • memPageList,正在使用的内存页的链表;
  • currUsePage,当前有最后一个内存页,下次分配时从此内存页上分配;
内存管页结构定义
内存页memPage作为向操作系统申请和释放的基本单位,它的定义如下:
  1. #define MEMORY_POOL_PAGE_SIZE (4096)
  2. #define MEMPAGE_INFO_LEN (sizeof(MemPageInfo))
  3. typedef struct MemPageInfo
  4. {
  5.     DList list;
  6.     int memPageSize;
  7.     int useOffset;
  8.     int freeSize;  
  9.     int releaseSize;         
  10. }MemPageInfo;
  11. typedef char *MemPage, *MemPtr;
复制代码
成员阐明:


  • list,由双向链表来记录全部的内存页;
  • memPageSize,当前内存页的巨细;大概有超大的内存页;
  • useOffset,使用内存的偏移;
  • freeSize,空闲内存巨细;
  • releaseSize, 释放内存巨细;记录当前内存页上使用过后,释放的内存巨细,当全部释放后,此内存页也会释放;
这里新定义了一定范例MemPage, 也就是char *的别名,这样在后面分配内存页时使用,方便区分。
内存管节点结构定义
应用程序每次申请内存,都会分配一个内存节点结构,包含了申请的内存巨细。
  1. /*
  2. * MemAlloc will alloc a MemBlock, and return MemBlock->ptr for user.
  3. */
  4. typedef struct MemBlock
  5. {
  6.     int size;                   /* memblock size + ptr[] size */
  7.     MemPageInfo *memPage;       /* current memPageInfo pointer */
  8.     char ptr[];
  9. }MemBlock;
复制代码
成员阐明:


  • size , 记录当前内存节点的巨细;
  • memPage,记录当前内存节点所属的内存块,指向内存页结构;
  • ptr, 返回给应用程序的内存首地址,这里没有定义巨细;在低版本编译器中大概不支持,可以指定数组巨细为1;
注意,这里ptr必须定义为数组,因为它就是数据的首地址,也就是一段内存的开始;如果定义为指针,需要手动赋值;
3.2 memPage 申请与释放

内存页的分配
当MemPage不够分配时,都需要从操作系统中申请一块新的memPage。
先从FreeList中查找,如果找到,就添加到当前使用列表中;
如果没有空闲memPage时,直接调用malloc举行分配。
  1. static int AddNewPageToContext(MemPoolManagerContext *poolContext)
  2. {
  3.     MemPageInfo *currentMemPage = NULL;
  4.     MemPage newPage = NULL;
  5.     newPage = GetFreeMemPage();
  6.     if(NULL == newPage)
  7.     {
  8.         newPage = (MemPage)malloc(MEMORY_POOL_PAGE_SIZE);
  9.         if(NULL == newPage)
  10.         {
  11.             exit(-1);
  12.         }
  13.     }        
  14.     currentMemPage = InitMemPage(newPage, MEMORY_POOL_PAGE_SIZE);
  15.         
  16.     /* add to context, and continue to alloc from context. */
  17.     AddMemPageToContext(currentMemPage, poolContext);      
  18.     return 0;
  19. }
复制代码
添加到使用列表中,同时将使用中的内存总数举行累加。
  1. static int AddMemPageToContext(MemPageInfo *memPage, MemPoolManagerContext *context)
  2. {
  3.     context->totalSize += memPage->memPageSize;      
  4.    
  5.     /* add to mempage list */
  6.     AddMemPageNode(&(context->memPageList), memPage);   
  7.     context->currUsePage = memPage;
  8.    
  9.     return 0;
  10. }
复制代码
此中AddMemPageNode是将新的内存页加到链表中,这个在链表章节介绍。
3.3 memNode 释放与释放

每次应用申请内存,都是从内存池中分配一个memNode。
当申请的size 小于当前内存页的空闲空间时,在当前页中分配一个memBlock结构;
  1. static MemPtr AllocFromMemPage(MemPageInfo *mPage, int size)
  2. {
  3.     MemBlock *memb = NULL;
  4.     mPage->freeSize -= size;
  5.     memb = (MemBlock*)((char *)mPage + mPage->useOffset);
  6.     mPage->useOffset += size;
  7.     memb->memPage = mPage;
  8.     memb->size = size;
  9.     return (MemPtr)(memb->ptr);
  10. }
复制代码
而当应用程序释放内存时,会将memBlock归还给对应的memPage;
在分配时,需要记录对应的memPage的地址,此时就可以举行引用。
  1. static int ReleaseToMemPage(MemBlock *memb)
  2. {
  3.     MemPageListInfo *memPageList = NULL;
  4.     MemPageInfo *memPage = memb->memPage;
  5.     DList *header = NULL;
  6.     int ret = 0;
  7.     if(NULL == memPage)
  8.     {
  9.         return -1;
  10.     }
  11.     memPage->releaseSize += memb->size;
  12.     return 0;
  13. }
复制代码
释放时只是将待释放的内存巨细增长到了 releaseSize;
当然这里可以将再举行优化,当memPage为空时,将它从使用列表中移除,并填加到空闲列表中。
3.4 应用申请内存

应用程序申请内存时,不能再使用malloc/free这两个接口了,需要使用自定义的接口。

动态内存申请接口
申请的流程如下:


  • 需要先定义一个全局的MemPoolManagerContext结构,来生存整个申请的内存页列表;通过GetMemPoolCurrentContext,可以达到单例获取的目的。
  • 申请的内存size需要增长MemBlock的头部成员的巨细;
  • 当申请的size大于内存页可分配的上限时,直接分配一个非尺度的内存页,将将它加到内存管理链表中;
  • 然后从内存管理中举行分配内存节点;
  1. #define MEMORY_POOL_BLOCK_HEADER_SIZE (sizeof(MemBlock))
  2. #define MEMPAGE_INFO_LEN (sizeof(MemPageInfo))
  3. #define MEMORY_POOL_MAX_ALLOC_SIZE (MEMORY_POOL_PAGE_SIZE - MEMPAGE_INFO_LEN)
  4. MemPtr AllocFromMemPool(unsigned int size)
  5. {
  6.     MemPtr mem = NULL;
  7.     MemPoolManagerContext *currentMemContext = NULL;
  8.     MemPageInfo *currentMemPage = NULL;
  9.     MemPage newPage = NULL;
  10.     currentMemContext = GetMemPoolCurrentContext();
  11.     if(NULL == currentMemContext)
  12.     {
  13.         return NULL;
  14.     }
  15.     /* add memblock header size */
  16.     size += MEMORY_POOL_BLOCK_HEADER_SIZE;
  17.     /* oversize */
  18.     if(size >= MEMORY_POOL_MAX_ALLOC_SIZE)
  19.     {
  20.         newPage = (MemPage)malloc(size);
  21.         currentMemPage = InitMemPage(newPage, size);
  22.         AddMemPageToContext(currentMemPage, currentMemContext);
  23.         mem = AllocFromMemPage(currentMemPage, size);
  24.         return mem;
  25.     }
  26.     do {
  27.         mem = AllocFromMemContext(size, currentMemContext);
  28.         if(NULL != mem)
  29.             break;
  30.         AddNewPageToContext(currentMemContext);     
  31.     }while(1);
  32.     return mem;
  33. }
复制代码
这里有一个小技巧,后面使用了一个do { }while(1)的循环,实在这里就是为了写法简单,当没有空闲空间时,会申请一个内存页,然后继续分配内存节点。
动态内存释放接口
这里相对简单,就是将当前内存节点释放到对应的内存页上。
  1. #define GetOffsetSize(type, member) (unsigned long)(&(((type *)(0))->member))
  2. #define GetAddrByMember(memberaddr, member, type) (type *)(((char*)(memberaddr)) - GetOffsetSize(type,member))
  3. #define GetMemBlockHeader(memptr) (GetAddrByMember(memptr, ptr, MemBlock))
  4. int ReleaseToMemPool(MemPtr mem)
  5. {
  6.     return ReleaseToMemPage(GetMemBlockHeader(mem));
  7. }
复制代码
在应用程序中拿到的是ptr数组首地址,通过它在结构体中的偏移,可以找到memBlock结构的地址,这里定义了一个宏。
当然可以使用offsetof,这个预定义的函数来获取结构体成员的偏移。
四、总结


在动态内存使用频仍的应用程序中,不仅与操作系统交互多,而且会造成大量的内存碎片,增长额外的系统负担。
本文分享了通过动态内存池的方法,每次申请一个内存页,然后在当有动态内存需要时,举行切分,可以避够内存碎片的产生。
当然也存在很多可优化的地方,在释放时可以生存一部分内存页在freeList中,这样进一步淘汰与操作系统的交互。
末端


   非常感谢各人的支持,在浏览的同时别忘了留下您名贵的品评,如果以为值得鼓励,请点赞,收藏,我会更加努力!
  作者邮箱:study@senllang.onaliyun.com
如有错误大概疏漏欢迎指出,互相学习。
注:未经同意,不得转载!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

自由的羽毛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表