修订
2013-04-15
- 代码中的New函数,使用DWORD保存4字节对齐后的大小,避免wSize接近65536时对齐为65536后溢出。
- Block结构添加prev指针,used及unused修改为双链表,避免Delete时的定位开销。
介绍
项目需要自定义一个表格控件,涉及到多行多列文本的显示。
考虑到字符串指针较多的情况下,性能较低以及容易产生内存碎片,为此实现了一个内存池。
该内存池具有以下特点:
- 不能分配接近及超过64k的内存(一般情况下,表格列不会包含如此长的文本)。
- 分配次数较多,单个释放次数较少(表格内容填充后不易变更,可以在删除表格时一次释放整个内存池)。
- 允许一定内存冗余,以减少内存碎片(单个表格需要的内存总量不会太大,可以接受较小比例的冗余)。
数据结构
首先,以64K为单位向系统申请内存作为内存区,供使用者按实际需要细分为内存块。
每块内存区是一个Area结构,每块内存块是一个Block结构。
当64k不够用的时候,重新申请一个Area,与当前所有已申请的Area组成一个链表。
Area
┌────┬─────┬───┬────┬────────────┐
│used│unsed│end│next│buffer │
│ │ │ │ ├───────┬────┤
│ │ │ │ │alloced│free│
└────┴─────┴───┴────┴───────┴────┘
used 指向Area内已分配且已使用的Block链表
ununed 指向Area内已分配且未使用的Block链表
end 指向已分配内存结束位置,新申请时从该位置分配内存
next 指向下一个Area,组成Area链表,以便在当前Area分配不足时申请新的Area
buffer 可分配内存,尺寸为64k减去前面几个字段的大小
Block
┌────┬────┬────┬────┬──────┐
│size│area│prev│next│memory│
└────┴────┴────┴────┴──────┘
size 表示实际可使用内存大小,以便Block重复使用时进行大小匹配
area 指向本Block所属Area
prev 指向上一个Block,组成used或unsed双链表
next 指向下一个Block,组成used或unsed双链表
算法
分配内存时,
首先查询所有Area的unused链表,如果找到合适的Block,可以直接使用。
如果没有合适的,在现有Area的待分配内存中分配新的Block。
如果现有Area的待分配内存都不满足需求大小,则重新申请一个Area并进行分配。
循环遍历Area链表 { 遍历unused链表 { 根据需要内存大小寻找最合适的已分配且未使用Block } } 如果找到合适的Block { 从所属Area的unsed链表移除 添加到所属Area的used链表 返回该Block的实际可使用内存 } 循环遍历Area链表 { 如果当前Area的待分配内存>=Block头+实际需要内存大小 { 按照Block头+实际需要内存大小分配Block 设置Block的size和area 移动Area的End位置 返回Block的实际可使用内存 } } 新申请Area 加入到Area链表 在新Area上申请Block
释放内存时,
首先获取指针所在的Block结构,然后通过Block获取所属Area
然后从Area的used链表移除Block。
最后将Block放入Area的unsed链表。
从内存指针减去Block头获取Block结构地址 遍历Block所属Area的used链表 { 如果找到 { 从used链表移除 } } 添加到所属Area的ununed链表
代码
为了节省内存空间,以及避免32位/64位系统不同指针长度带来的不确定性,Area及Block内的各种指针尽量使用WORD表示0-65535的偏移值来代替。
// MemPool.h #pragma once //------------------------------------------------------------------------------ // 不能分配大于 AREA_SIZE-sizeof(Block) 的内存 class MemPool { struct Block; struct Area { WORD pUsed; // 已使用的首个内存块距离Area首地址的偏移 WORD pUnused; // 未使用的首个内存块距离Area首地址的偏移 WORD wEnd; // 当前已分配内存距离首地址pBuffer的偏移 WORD wReserved; // 保留以便对齐 Area *pNext; #define HEAD_SIZE (sizeof(WORD) + sizeof(WORD) + sizeof(WORD) + sizeof(WORD) + sizeof(Area*)) #define AREA_SIZE (65536 - HEAD_SIZE) char pBuffer[AREA_SIZE]; // 预分配内存 }; struct Block { WORD wSize; // 分配尺寸 WORD pArea; // Block距离所属Area首地址的偏移 WORD pPrev; WORD pNext; }; public: MemPool(void); ~MemPool(void); void Cleanup(void); void *New(WORD wSize); void Delete(void *pPointer); private: Area m_xArea; void *New(Area *pArea, WORD wSize); }; // MemPool.cpp #include <assert.h> #include <Windows.h> #include "MemPool.h" //------------------------------------------------------------------------------ MemPool::MemPool(void) { memset(&m_xArea, 0, sizeof(m_xArea)); } MemPool::~MemPool(void) { Cleanup(); } void MemPool::Cleanup(void) { for (Area *pNext=m_xArea.pNext; NULL!=pNext; ) { Area *pDelete = pNext; pNext = pNext->pNext; delete pDelete; } memset(&m_xArea, 0, sizeof(m_xArea)); } void *MemPool::New(WORD wSize) { DWORD wNeedSize = ((wSize+3) & ~3); // 四字节对齐 assert(wNeedSize <= AREA_SIZE - sizeof(Block)); // 查找最合适的未使用内存块 long lBestOffset = 65536; Block *pBestBlock = NULL; for (Area *pArea=&m_xArea; NULL!=pArea; pArea=pArea->pNext) { for (WORD pUnused=pArea->pUnused; 0!=pUnused; ) { Block *pBlock = (Block*)((char*)pArea + pUnused); long lOffset = pBlock->wSize - wNeedSize; if (lOffset>=0 && lOffset<lBestOffset) { lBestOffset = lOffset; pBestBlock = pBlock; } pUnused = pBlock->pNext; } } if (NULL!=pBestBlock && lBestOffset<32) // 有未使用内存块且冗余可以接受 { Area *pArea = (Area *)((char*)pBestBlock - pBestBlock->pArea); // 从未使用列表中移除 if (0 != pBestBlock->pPrev) { Block *pPrevBlock = (Block*)((char*)pArea + pBestBlock->pPrev); pPrevBlock->pNext = pBestBlock->pNext; } else pArea->pUnused = pBestBlock->pNext; if (0 != pBestBlock->pNext) { Block *pNextBlock = (Block*)((char*)pArea + pBestBlock->pNext); pNextBlock->pPrev = pBestBlock->pPrev; } // 添加到使用列表 if (0 != pArea->pUsed) { Block *pPrevBlock = (Block*)((char*)pArea + pArea->pUsed); pPrevBlock->pPrev = pBestBlock->pArea; } pBestBlock->pNext = pArea->pUsed; pArea->pUsed = pBestBlock->pArea; return (char*)pBestBlock + sizeof(Block); } // 重新分配 Area *pLastArea = NULL; // 在已有内存上分配新内存块 for (Area *pArea=&m_xArea; NULL!=pArea; pArea=pArea->pNext) { if ((long)wNeedSize <= (long)(AREA_SIZE - sizeof(Block) - pArea->wEnd)) return New(pArea, (WORD)wNeedSize); pLastArea = pArea; } // 重新申请内存 if (Area *pArea = new Area) { memset(pArea, 0, sizeof(Area)); pLastArea->pNext = pArea; return New(pArea, (WORD)wNeedSize); } return NULL; } void MemPool::Delete(void *pPointer) { Block *pBlock = (Block *)((char *)pPointer - sizeof(Block)); Area *pArea = (Area *)((char*)pBlock - pBlock->pArea); // 从使用列表中移除 if (0 != pBlock->pPrev) { Block *pPrevBlock = (Block*)((char*)pArea + pBlock->pPrev); pPrevBlock->pNext = pBlock->pNext; } else pArea->pUsed = pBlock->pNext; if (0 != pBlock->pNext) { Block *pNextBlock = (Block*)((char*)pArea + pBlock->pNext); pNextBlock->pPrev = pBlock->pPrev; } // 添加到未使用列表 if (0 != pArea->pUnused) { Block *pPrevBlock = (Block*)((char*)pArea + pArea->pUnused); pPrevBlock->pPrev = pBlock->pArea; } pBlock->pNext = pArea->pUnused; pArea->pUnused = pBlock->pArea; } void *MemPool::New(Area *pArea, WORD wSize) { // 分配新内存块 Block *pBlock = (Block*)((char*)pArea + HEAD_SIZE + pArea->wEnd); pBlock->pArea = HEAD_SIZE + pArea->wEnd; pBlock->wSize = wSize; // 添加到使用列表 if (0 != pArea->pUsed) { Block *pPrevBlock = (Block*)((char*)pArea + pArea->pUsed); pPrevBlock->pPrev = pBlock->pArea; } pBlock->pNext = pArea->pUsed; pArea->pUsed = (char*)pBlock - (char*)pArea; pArea->wEnd += sizeof(Block) + wSize; return (char*)pBlock + sizeof(Block); }
其他
分配时查找unused链表需要耗费一定时间,后续可以考虑采用辅助算法加速查找。