• 简易内存池实现


    修订

    2013-04-15

    1. 代码中的New函数,使用DWORD保存4字节对齐后的大小,避免wSize接近65536时对齐为65536后溢出。
    2. Block结构添加prev指针,used及unused修改为双链表,避免Delete时的定位开销。

    介绍

    项目需要自定义一个表格控件,涉及到多行多列文本的显示。
    考虑到字符串指针较多的情况下,性能较低以及容易产生内存碎片,为此实现了一个内存池。
    该内存池具有以下特点:

    1. 不能分配接近及超过64k的内存(一般情况下,表格列不会包含如此长的文本)。
    2. 分配次数较多,单个释放次数较少(表格内容填充后不易变更,可以在删除表格时一次释放整个内存池)。
    3. 允许一定内存冗余,以减少内存碎片(单个表格需要的内存总量不会太大,可以接受较小比例的冗余)。

    数据结构

    首先,以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链表需要耗费一定时间,后续可以考虑采用辅助算法加速查找。

  • 相关阅读:

    双向链表和环形链表(约瑟夫问题)
    单向链表的增删查改
    稀疏数组与环形数组
    离焦事件。这个坑谁抗的住呀,好无语呀
    maven
    maven工程运行出Unable to compile class for JSP: 错误
    笔记-JavaWeb学习之旅19
    获取redis cluster master对应的slot分布情况
    批量获取mysql数据库实例指定参数的值
  • 原文地址:https://www.cnblogs.com/armageddon/p/3020110.html
Copyright © 2020-2023  润新知