by 沉小猫
在程序中我们经常要分配内存,回收内存,在一般的地方实时分配与回收造成的性能损失不会对程序造成什么影响。但是在有些程序中,计算强度很高,内存分配与回收又很频繁,比如粒子系统,帧动画系统中,粒子的产生与死亡,动画的产生与死亡,都会很频繁的分配内存与释放内存。显然在这里不能实时分配与回收内存,通常的做法是预先分配一块大的内存,当要用时从里面取,当不够时再分配,用链表将已分配的内存链接起来,这就是通常意义上的内存池。
我要实现的内存池是限于一个指定对象的内存池,他里面存放的都是同一对象,里面是什么对象,由用户在实例化该内存池时通过模板指定。
内存池的实现主要用链表实现,主要的数据结构如下:
// 元素结点结构
typedef struct ELEMENT_NODE
{
ELEMENT_NODE* pPreNode; // 指向前一个结点的指针
void* pElement; // 指向该结点元素的指针
ELEMENT_NODE* pNextNode; // 指向下一个结点的指针
}ELEMENT_NODE, *PELEMENT_NODE;
// 内存单元结点结构
typedef struct MEMORY_UNIT_NODE
{
MEMORY_UNIT_NODE* pPreNode; // 指向前一个结点的指针
void* pUnit; // 指向当前单元的指针
MEMORY_UNIT_NODE* pNextNode; // 指向下一个结点的指针
ELEMENT_NODE* pUnitENList; // 指向该单元的元素节点序列
}MEMORY_UNIT_NODE, *PMEMORY_UNIT_NODE;
MEMORY_UNIT_NODE 为内存池中各个存在的实际内存单元。pUnit为分配的保存元素内容内存,pUnitENList为该内存单元中的元素结点序列,一个元素结点(ELEMETN_NODE)中的pElement指针指向pUnit指向的元素内容中的一个元素。所有先后分配的内存单元由链表链接,所以只要记下内存单元链表的首结点和尾结点就能很方便的添加新的内存单元,也能做到很好的内存释放。
ELEMENT_NODE 为指定对象的结点,就是每个从内存池中取出的对象,也是由双向链表链接。
它们具体的链接示意图如下:
红色的箭头表示每个分配的内存单元的链接情况,它为一个双向链表,程序中只保存该链表的首尾指针。
蓝色箭头表示每个内存单元中每个元素结点指向实际的元素内容(就是特定的对象)内存指针。
黑色箭头表示所有未使用的的元素结点,也是用双向链表链接的。从内存池中取出的元素记为使用的元素,当归还该元素后将会将其加到未使用元素链表的最后。
内存池初始化时会指定要存储的对象类型,初始分配一个内存单元,并指定这个内存单元中有多少个元素结点,并指定以后动态增长内存单元链表时的内存单元大小。这里要做一些初始化的工作,分配内存不用说,主要是链接各链表。
首先要将内存单元中的pUnitENList 元素序列中每个元素的 pElement指针指向该内存单元中的 pUnit 指向的元素内存相应的位置;接着就是要链接pUnitENList中的各结点。最近初始化内存单元链表首尾结点,未使用元素结点序列首尾结点,并初始化各计数。
这里给出取元素和删除元素的实现过程,有两个接口:
// 从内存池中取一个元素函数(此函数会重新链接余下的元素序列)
// 成功返回指向该元素结点的指针
// 失败返回NULL
ELEMENT_NODE* GetElement();
// 删除指定的元素函数
// pNode:指向元素的指针
bool Delete( ELEMENT_NODE* pNode );
第一个函数是从内存池中取元素(ELEMENT_NODE),返回一个ELEMENT_NODE指针,这个指针指向的pElement为元素内容,使用时须强转一下指针类型。在这里不能直接返回指向该元素的指针,因为为了能在回收元素时保持未使用元素序列的链接性,必须知道取出元素的ELEMETN_NODE,这样才能在回收该元素时能将其链接到未使用的元素ELEMENT_NODE链表尾端。在取元素时须先判断是否还有未使用的元素,如果没有则要新分配一个内存单元,这里也要做该内存单元的初始化工作,过程类似于前面说的初始化内存池时做的工作。因为元素的回收不关心该元素的前向(pPreNode)和后向指针(pNextNode),所以取出的元素可以在外部修改它的前向和后向指针,但要注意回收后该元素的前向和后向指会被修改。
第二个函数回收取出的元素,它做的工作只是将要回收的元素重新链接到未使用元素序列的尾端,并更新相关计数。
由于该内存池类的操作几乎都是指针操作,所取元素和回收元素速度非常快。只是在每次分配新的内存单元时要多耗一些时间。不过可以指定初始分配大小和以后的动态增长大小,这就要在牺牲内存和速度上取一个好的平衡点。该内存池没有智能回收内存单元功能,因为考虑到要做到智能内存回收的话就必须要在速度上做一些损失,内存池中内存单元的增长会随着取出了最多的元素而达到一个峰值,不过在内存池对象析构的时侯会释放所有的内存。
该内存池在取元素和回收元素上速度非常快,对于像类似于粒子系统,帧动画系统这样的大量使用相同数据结构的系统,用它来做内存管理非常适合。
在这里就不列出代码了,关于各接口的声明大家可以参考一下:
// 内存池类-------------------------------------------------------------------------
template <typename T>
class CatMemoryPool
{
public:
CatMemoryPool();
~CatMemoryPool();
// 构造函数,此构造函数会初始化内存池,可避免后面的 Init() 调用
CatMemoryPool( int nUnitSize );
// 初始化内存池函数(注意:如果用默认构造函数初始化内存池对象则必须显式调用此初始化函数)
bool Init( int nUnitSize );
// 重设内存分配单元大小函数
void ResetUnitSize( int nSize );
// 释放函数
void Release();
// 从内存池中取一个元素函数(此函数会重新链接余下的元素序列)
// 成功返回指向该元素结点的指针
// 失败返回NULL
ELEMENT_NODE* GetElement();
// 删除指定的元素函数
// pNode:指向元素的指针
bool Delete( ELEMENT_NODE* pNode );
// 得到未使用的元素链表的指针函数
ELEMENT_NODE* GetUnusedElementList() const;
// 得到当前分配的内存单元数
inline int GetMemoryUnitNum() const;
// 得到当前的结点总数
inline int GetElementNum() const;
// 得到当前已使用的结点数
inline int GetUsedElementNum() const;
// 得到当前分配的内存大小
inline unsigned long GetAllocMemorySize() const;
private:
int m_nUnitSize; // 每个内存单元中的元素数数目
int m_nMemoryUnitNum; // 内存单元数目(不能保证每个单元中的结点数相同)
int m_nElementNum; // 结点总数
int m_nUsedElementNum; // 已使用的结点数
unsigned long m_ulAllocMemorySize; // 已分配的内存大小(字节)
MEMORY_UNIT_NODE* m_pFirstMUN; // 内存单元序列指针
MEMORY_UNIT_NODE* m_pLastMUN; // 指向内存单元序列最后一个单元的指针
ELEMENT_NODE* m_pFirstUnusedEN; // 指向未使用元素序列第一个节点指针
ELEMENT_NODE* m_pUnusedLastEN;// 指向未使用元素序列最后一个元素的指针
};
由于这无法上传源代码,如果需要,请给我发邮件: