【玩具】内存池分配

简介: 采用系统的内存分配策略,程序运行久了容易产生内存碎片,而且每次都从系统申请,然后释放归还给系统会消耗一定的性能。当然现在基本上都有现成的内存分配框架,例如tcmalloc。自己写除非是特殊应用场景,否则一般不会比系统默认的那套性能高效。本篇就介绍一个简单的内存池,属于玩具系列,不可在正式项目中使用。

【玩具】内存池分配


一、引言


   采用系统的内存分配策略,程序运行久了容易产生内存碎片,而且每次都从系统申请,然后释放归还给系统会消耗一定的性能。当然现在基本上都有现成的内存分配框架,例如tcmalloc。自己写除非是特殊应用场景,否则一般不会比系统默认的那套性能高效。本篇就介绍一个简单的内存池,属于玩具系列,不可在正式项目中使用。


二、简单内存池

微信图片_20220424113929.png


   根据上图,可以很清楚的了解这个内存池的设计方式,按照申请分配的内存大小进行分组,大小相同的被放在同一个链表里面。可用和已用链表分开,方便快速进行拿取和回收。具体的结构体如下:


/*
* 内存分配的块
*/
typedef struct _QMBlock
{
  struct _QMBlock* pNext; //指向下一块
  struct _QMBlock* pPrev; //指向上一块
  void * pData; // 内存数据
}QMBlock; 
/*
* 用于存储分配对应内存大小的块的列表
*/
typedef struct _QMBlockList
{
  QMBlock* pUsedBlock; //已使用的内存块(头)
  QMBlock* pLastUsedBlock; //已使用的内存块(尾)
  QMBlock* pFreeBlock; // 空闲的内存块(头)
  QMBlock* pLastFreeBlock; //空闲的内存块(尾)
  struct _QMBlockList* pNext; //指向下一个内存块列表
  size_t mSize; // 当前内存块列表中块的大小
}QMBlockList;


       具体实现就是申请内存时,先从QMBlockList链表中找有没有相同mSize大小的链表,没有则创建新的;找到QMBlockList后再看里面有没有可用的QMBlock内存块,如果没有则创建新的,找到则从可用链表移除。将可用QMBlock设置到已用链表中,然后设置校验信息并返回对应内存指针。


void* BindBlockData(QMBlockList* block_list, QMBlock* block)
{
#ifdef OPEN_BLOCK_HEAD_CHECK
  int* temp_ptr = (int*)block->pData;
  temp_ptr[0] = BLOCK_HEAD_TAG; //注意这里只能占用4个字节
  intptr_t* ptr = (intptr_t*)(((char*)block->pData)+sizeof(int)); //强转为指针类型(跨平台)
  ptr[0] = (intptr_t)block_list;
  ptr[1] = (intptr_t)block; 
  return &ptr[2];
#else
  intptr_t* ptr = (intptr_t*)block->pData; //强转为指针类型(跨平台)
  ptr[0] = (intptr_t)block_list;
  ptr[1] = (intptr_t)block;
  return &ptr[2];
#endif // OPEN_BLOCK_HEAD_CHECK
}


   外部使用的ptr指针地址其实前面有一段记录它属于哪个QMBlockListQMBlock,并额外加入正确性校验。外部释放的时候则先拿到所属的对应QMBlockListQMBlock,然后进行校验指针是否是对的,最后从已用链表移到可用链表。


int UnbindBlockData(void* p, QMBlockList** block_list, QMBlock** block
{
  intptr_t* ptr = (intptr_t*)p; //强转
  *block_list = (QMBlockList*)(*(ptr - 2)); //往前减
  *block = (QMBlock*)(*(ptr - 1));
#ifdef OPEN_BLOCK_HEAD_CHECK
  char* temp_ptr = ((char*)p) - BLOCK_FLAG_SIZE;
  int check_k = *(int*)(temp_ptr);
  if (check_k != BLOCK_HEAD_TAG)
  {  //校验失败了
    return -1;
  }
#endif // OPEN_BLOCK_HEAD_CHECK
  return 0;
}


三、总结


   实现内存池的好处不仅包括快速的分配和回收,还包括内存整理、防止重复释放、统计已分配的内存数量、定位分配的位置等等。但是一般写的内存分配99%可能性没有内置的好,且内存出bug难调试,大神除外。以上我写的就是个简单的玩具,源码放在GitHub上,欢迎大家批评教育。


欢迎微信搜索"游戏测试开发"关注一起沟通交流。

相关文章
|
6月前
3.1.2 内存池的实现与场景分析
3.1.2 内存池的实现与场景分析
|
算法 应用服务中间件 nginx
超越内存限制:深入探索内存池的工作原理与实现
这篇文章将深入探索内存池的工作原理与实现,介绍如何超越传统的内存限制。首先,我们将了解什么是内存池以及它与传统内存分配方式的不同之处。接着,我们将探索内存池的工作原理,包括内存池的数据结构和算法。我们还将解释内存池如何提升性能,避免内存碎片化,并减少内存分配的开销。此外,我们将介绍一些常见的内存池实现技术,例如固定大小内存池和动态大小内存池,并对比它们的优劣之处。
123 0
|
5月前
|
存储 缓存 监控
深入解析JVM内存分配优化技术:TLAB
深入解析JVM内存分配优化技术:TLAB
|
6月前
|
存储 程序员 C语言
动态内存:灵活分配
动态内存:灵活分配
|
6月前
|
缓存 算法 安全
Linux内存管理宏观篇(五)物理内存:页面分配和释放页面
Linux内存管理宏观篇(五)物理内存:页面分配和释放页面
360 1
|
6月前
|
应用服务中间件 nginx
内存池的实现与分析
内存池的实现与分析
51 0
|
6月前
|
存储 算法 Java
某操纵系统采用可变分区分配存储管理方法,用户区为512K且始址为0,用空闲分区表管理空闲分区。若分配是采用分配空闲区低地址部分的方案,且初始时用户区的512K空间空闲,对下述申请序列:申请300K,申
某操纵系统采用可变分区分配存储管理方法,用户区为512K且始址为0,用空闲分区表管理空闲分区。若分配是采用分配空闲区低地址部分的方案,且初始时用户区的512K空间空闲,对下述申请序列:申请300K,申
94 0
|
6月前
|
存储
内存池的实现与场景分析
内存池的实现与场景分析
68 0
|
存储 算法 程序员
内存管理(二)——连续分配管理方式
内存管理(二)——连续分配管理方式
393 0
|
存储 Oracle Java
JVM是如何分配管理内存的?
JVM是如何分配管理内存的?
117 0