【玩具】内存池分配

简介: 采用系统的内存分配策略,程序运行久了容易产生内存碎片,而且每次都从系统申请,然后释放归还给系统会消耗一定的性能。当然现在基本上都有现成的内存分配框架,例如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 内存池的实现与场景分析
|
5月前
|
存储 缓存 监控
深入解析JVM内存分配优化技术:TLAB
深入解析JVM内存分配优化技术:TLAB
|
6月前
|
缓存 算法 Linux
Linux内存管理宏观篇(六)物理内存:分配小内存块
Linux内存管理宏观篇(六)物理内存:分配小内存块
106 1
|
6月前
|
应用服务中间件 nginx
内存池的实现与分析
内存池的实现与分析
52 0
|
6月前
|
缓存 Linux
内存学习(八):块分配器1
内存学习(八):块分配器1
67 0
|
6月前
|
存储
内存池的实现与场景分析
内存池的实现与场景分析
71 0
|
存储 程序员 C语言
C++动态内存的分配、使用、释放
C++动态内存的分配、使用、释放
|
算法 程序员 调度
3.1操作系统(内存管理的概念 分配与回收 空间的扩充)
一.内存 1.什么是内存?有何作用? 几个常用的数量单位 2.进程运行的基本原理 1. 指令的工作原理 2.逻辑地址vs物理地址 3.从写程序到程序运行 4.装入的三种方式 1.绝对装入 2. 可重定位装入(静态重定位) 3. 动态运行时装入(动态重定位) 5.链接的三种方式 1. 静态链接 2. 装入时动态链接 3. 运行时动态链接 二、内存管理的概念 1.内存空间的分配与回收 1.单一连续分配 2. 固定分区分配 3. 动态分区分配 (1)系统要用什么样的数据结构记录内存的使用情况? (2)当很多个空闲分区都能满足需求时,应该选择哪个分区
3.1操作系统(内存管理的概念 分配与回收 空间的扩充)
|
容器
一.空间分配器
# 一.空间分配器 ### 分配内存: * 当容器需要空间来存放元素时,需要空间配置器(也就是分配器)分配内存,当分配的内存大于128个字节时,调用第一级配置器,调用malloc为其分配内存,当分配内存小于128个字节时,调用第二级配置器,检查对应的free-list上是否有可用区块,如果有的话,直接拿来用,如果没有的话调用rfill。
81 0
|
存储 Java 程序员
【朝花夕拾】Android性能篇之(二)Java内存分配
相比于C/C++,Java通过JVM自动完成内存的分配和回收,为Java程序员带来了极大的便利。但也正因为如此,由于平时对JVM的依赖,当内存碰到如内存泄漏等问题时,往往束手无策。所以,充分了解Java内存及其分配,是一个资深程序员的必备内功之一。
1860 0