【玩具】内存池分配

简介: 采用系统的内存分配策略,程序运行久了容易产生内存碎片,而且每次都从系统申请,然后释放归还给系统会消耗一定的性能。当然现在基本上都有现成的内存分配框架,例如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上,欢迎大家批评教育。


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

相关文章
【数值分析】二分法求方程的根(附matlab代码)
【数值分析】二分法求方程的根(附matlab代码)
|
移动开发 前端开发 安全
React Native环境搭建及配置问题
React Native环境搭建及配置问题
296 2
|
存储 缓存 算法
【Conan 入门教程 】了解 Conan2.1 中默认生成器的作用
【Conan 入门教程 】了解 Conan2.1 中默认生成器的作用
298 1
|
缓存 JavaScript 前端开发
Vue.js计算属性:实现数据驱动的利器
Vue.js计算属性:实现数据驱动的利器
|
存储 Linux PHP
云环境下使用NAS搭建个人网盘
根据云起实验室提供的环境。记录一下在云环境下使用NAS搭建个人网盘,安装及初始配置。
1417 1
|
机器学习/深度学习 搜索推荐 PyTorch
【机器学习】图神经网络:深度解析图神经网络的基本构成和原理以及关键技术
【机器学习】图神经网络:深度解析图神经网络的基本构成和原理以及关键技术
3001 3
|
机器学习/深度学习 人工智能 达摩院
|
存储 缓存 Rust
深入浅出 tnpm rapid 模式 - 如何比 pnpm 快 10 秒
深入浅出 tnpm rapid 模式 - 如何比 pnpm 快 10 秒
508 1
|
算法 API 计算机视觉
【OpenCV图像处理5】图像的变换
【OpenCV图像处理5】图像的变换
355 0
|
前端开发 Java Apache
基于Springboot外卖系统13:实现文件上传下载模块
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。
329 0