如何设计一个简单内存池

Posted 蓝桥杯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何设计一个简单内存池相关的知识,希望对你有一定的参考价值。



什么是内存池???

  

通常我们用new或malloc来分配内存的话,由于申请的大小不确定,所以当频繁的使用时会造成内存碎片和效率的降低。为了克服这种问题我们提出了内存池的概念。内存池是一种内存分配方式。内存池的优点就是可以有效的减少内存碎片化,分配内存更快速,减少内存泄漏等优点。

  

内存池是在真正使用内存之前,先申请分配一个大的内存块留作备用。当真正需要使用内存的时候,就从内存池中分配一块内存使用,当使这块用完了之后再还给内存池。若是内存块不够了就向内存再申请一块大的内存块。

  

可以看出这样做有两个好处:

  

1、由于向内存申请的内存块都是比较大的,所以能够降低外碎片问题。

2、一次性向内存申请一块大的内存慢慢使用,避免了频繁的向内存请求内存操作,提高内存分配的效率。


内存碎片化:

  

造成堆利用率很低的一个主要原因就是内存碎片化。如果有未使用的存储器,但是这块存储器不能用来满足分配的请求,这时候就会产生内存碎片化问题。内存碎片化分为内部碎片和外部碎片。


内碎片:

  

内部碎片是指一个已分配的块比有效载荷大时发生的。(举个栗子:假设以前分配了10个大小的字节,现在只用了5个字节,则剩下的5个字节就会内碎片)。内部碎片的大小就是已经分配的块的大小和他们的有效载荷之差的和。因此内部碎片取决于以前请求内存的模式和分配器实现的模式。


如何设计一个简单内存池

外碎片:

  

外部碎片就是当空闲的存储器的和计起来足够满足一个分配请求,但是没有一个单独的空闲块足够大可以处理这个请求。外部碎片取决于以前的请求内存的模式和分配器的实现模式,还取决于于将来的内存请求模式。所以外部碎片难以量化。

如何设计一个简单内存池

  

  

下面介绍一种简单的内存池,它是针对于某种对象实现的。我们可以用一个链表实现这个内存池,链表上的每个结点都是一个对象池,如果我们需要申请空间的话,直接去内存池里面申请空间,当用完之后再还给内存池。


如何设计一个简单内存池


内存池的设计主要包含三步:


1、初始化

  在创建内存池的时候为内存池分配了一块很大的内存,便于以后的使用。


2、分配内存

  当需要内存的时候就去内存池里面分配内存。


3、回收内存

  当从内存池里面分配来的内存使用完毕之后,需要将这块内存还给内存池。


设计上面这个内存池最重要的问题就是如何重复利用释放回来的内存,让利用率达到最高???



但是如果当对象的大小小于对象指针的时候,也就是一个对象的空间存不下一个指针的大小,这时候就不可避免的产生内碎片。

  

例如:为T类型对象开辟对象池,sizeof(T)<sizeof(T*),这时候我们就要为一个T类型对象申请sizeof(T*)大小的内存。



  1. 代码实现:  

  2. #pragma once  

  3. #include<iostream>  

  4. using namespace std;  

  5. //用链表来实现内存池,每一个结点都挂有一块内存  

  6. template<typename T>  

  7. class ObjectPool  

  8. {  

  9.        struct BlockNode         //每一个结点类型  

  10.        {  

  11.               void* _memory;        //指向一块已经分配的内存  

  12.               BlockNode * _next;    //指向下一个结点  

  13.               size_t _objNum;       //记录这块内存中对象的个数  

  14.               BlockNode(size_t objNum)  

  15.                      :_objNum(objNum)  

  16.                      , _next(NULL)  

  17.               {  

  18.                      _memory = malloc(_objNum*_itemSize);  

  19.               }  

  20.               ~BlockNode()  

  21.               {  

  22.                      free(_memory);  

  23.                      _memory = NULL;  

  24.                      _next = NULL;  

  25.                      _objNum = 0;  

  26.               }  

  27.        };  

  28. protected:  

  29.        size_t _countIn;      //当前结点的在用的计数  

  30.        BlockNode* _frist;    //指向链表的头  

  31.        BlockNode* _last;     //指向链表的尾  

  32.        size_t _maxNum;        //记录内存块最大的容量  

  33.        static size_t _itemSize;   //单个对象的大小  

  34.        T* _lastDelete;        //指向最新释放的那个对象的空间  

  35. public:  

  36.        ObjectPool(size_t initNum = 32, size_t maxNum = 100000)  //默认最开始内存块有32个对象,一个内存块最大有maxNum个对象  

  37.               :_countIn(0)  

  38.               , _maxNum(maxNum)  

  39.               , _lastDelete(NULL)  

  40.        {  

  41.               _frist = _last =new BlockNode(initNum);   //先开辟一个结点,这个结点里面的内存块能够存放initNum个对象  

  42.        }  

  43.        ~ObjectPool()  

  44.        {  

  45.               Destory();  

  46.        }  

  47.        T* New()                   //分配内存  

  48.        {  

  49.               if (_lastDelete)         //先到释放已经用完并且换回来的内存中去找  

  50.               {  

  51.                      T* object = _lastDelete;  

  52.                      _lastDelete = *((T**)_lastDelete);  //将_lastDelete转换成T**,*引用再取出来T*,也就是取出前T*类型大小的单元  

  53.                      return new(object) T();        //把这块内存用从定位new初始化一下  

  54.               }  

  55.               //判断还有没有已经分配的内存且还未使用,如果没有内存的话就要再分配内存  

  56.               if (_countIn >= _last->_objNum)     //大于等于表示没有了,这时候就要分配内存了  

  57.               {  

  58.                      size_t size =2*_countIn;  

  59.                      if (size > _maxNum)            //块的最大大小不能超过maxNum,如果没超过就以二倍增长  

  60.                            size = _maxNum;  

  61.                      _last->_next = new BlockNode(size);  

  62.                      _last = _last->_next;  

  63.                      _countIn = 0;  

  64.               }  

  65.               //还有已经分配好的未被使用的内存  

  66.               T* object =(T*)_last->_memory + _countIn;  

  67.               _countIn++;  

  68.               return new(object) T();        //将这块空间用重定位new初始化一下  

  69.        }  

  70.        void Destory()  

  71.        {  

  72.               BlockNode *cur = _frist;  

  73.               while (cur)  

  74.               {  

  75.                      BlockNode* del = cur;  

  76.                      cur = cur->_next;  

  77.                      delete del;            //会自动调用~BlockNode()  

  78.               }  

  79.               _frist = _last = NULL;  

  80.        }  

  81.        void Delete(T* object)          //释放内存  

  82.        {  

  83.               if (object)  

  84.               {  

  85.                      object->~T();         

  86.                      _lastDelete = object;  

  87.               }  

  88.        }  

  89. protected:  

  90.        static size_t GetItemSize()  

  91.        {  

  92.               if (sizeof(T)>sizeof(T*))  

  93.               {  

  94.                      return sizeof(T);  

  95.               }  

  96.               else  

  97.               {  

  98.                      return sizeof(T*);  

  99.               }  

  100.        }  

  101. };  

  102. template<typename T>  

  103. size_t ObjectPool<T>::_itemSize =ObjectPool<T>::GetItemSize();          //类外初始化静态变量_itemSize  







以上是关于如何设计一个简单内存池的主要内容,如果未能解决你的问题,请参考以下文章

网易一面:如何设计线程池?请手写一个简单线程池?

实现一个简单的内存池

简单内存池与定长内存池

简单内存池与定长内存池

最简单的设计模式——单例模式

Linux C 一个简单的线程池程序设计