MyException - 我的异常网
当前位置:我的异常网» 综合 » 最容易的内存池-原理与实现

最容易的内存池-原理与实现

www.MyException.Cn  网友分享于:2013-11-01  浏览:19次
最简单的内存池-原理与实现

    内存池的主要作用,简单地说来,便是提高内存的使用效率。堆内存的申请与释放(new/delete及malloc/free),涉及复杂的内存分配算法, 相比由简单CPU指令支持的栈内存的申请与释放,则是慢上了数量级。另一方面,栈的大小是有限制的,在需要大量内存的操作时,堆的使用是必要的。当然,频繁地申请与释放堆内存,效率是很低的,这也就是内存池出现的原因。
    类似std::vector容器,在向vector里添加一个元素之前,该容器往往提前申请了一大块内存,实际添加时就只需要拿出其中一小块来使用,不用每添加一个都使用new一次。内存池所做的,也就是一次申请一块大内存,然后再小块小块地拿出来用:一次申请NM大小的内存,必然比N次申请M大小的内存要快,这就是内存池高效的简单解释。
    一般来说,内存池可以按两种维度划分:单线程与多线程,可变大小和固定大小。其中最简单的单线程固定大小的内存池。这类内存池只考虑单线程的程序,分配与回收的对象都是固定大小的。这个简单的内存池类模板声明如下:

template<typename T, int BaseSize=32>
class SimpleMemPool
{
private:
#pragma pack(push, 1)
    union ObjectChunk
    {
        ObjectChunk * next;
        char buf[sizeof(T)];
    }; 
    struct MemBlock
    {
        MemBlock* next;
        ObjectChunk chunks[BaseSize];
    };
#pragma pack(pop)

    SimpleMemPool(const SimpleMemPool<T, BaseSize> &) ;
   
    MemBlock * head;
    ObjectChunk * freeChunk;
public:
    SimpleMemPool();
    ~SimpleMemPool();

    T* New();
    void Delete(T* t);
};

 

    该内存池类模板实例化之后,产生一个只用于申请对象T的内存池。创建对象的方法是T* New(),删除对象的方法是void Delete(T *)。类模板中还定义了两个数据结构及两个成员变量,还禁止了内存池的拷贝构造。对于这种内存池,它有两层的链表结构:第一层是内存块链表(MemBlock),由成员head为头,把内存池中申请的大块内存串接起来。MemBlock结构的next指针指向了下一个内存块,chunks则 为BaseSize个ObjectChunk对象。该链表的每个结点都是内存池进行一次申请得到的大内存块;第二层为自由内存块链表 (ObjectChunk),由freeChunk为头指针串着,该链表的每一个结点,都是一个可以使用的小内存块。大体结构如下图(BaseSize为 5)所示:


 
    当用户向内存池申请一个对象T时,内存池会检查freeChunk是否为NULL。不为NULL时,表示还有自由的小内存块(ObjectChunk)可供使用,将它返回给用户,并将freeChunk移到下一个自由内存块;若freeChunk为NULL,则内存池需要使用 new/malloc从申请一块大内存(MemBlock),然后将其中的BaseSize个ObjectChunk都串连上形成链表,再将 freeChunk做为头指针。这时freeChunk不为NULL了,可以提供给用户一块小内存了(具体即是,取出链表的首结点)。

template<>
class SimpleMemPool
{
    T* New()
    {
        if (!freeChunk)
        {
            MemBlock * t = new MemBlock;
            t->next = head;
             head = t;
            for(unsigned int i=0; i<BaseSize-1;++i)
            {
                head->chunks[i].next = & (head->chunks[i+1]);
            }
            head->chunks[BaseSize-1].next = 0;
            freeChunk = & (head->chunks[0]);
        }

        ObjectChunk * t = freeChunk;
        freeChunk = freeChunk->next;
        return reinterpret_cast<T*>(t);
    }
};

 

    当用户不再使用某块小内存时,需要调用Delete方法。此时,内存池把用户不用的小块内存,重新连接到以freeChunk为首指针的链表中就行了(将结点插入链表的首位置)。代码如下:

template<>
class SimpleMemPool
{
    void Delete(T* t)
    {
        ObjectChunk * chunk = reinterpret_cast<ObjectChunk*>(t);
        chunk->next= freeChunk;
        freeChunk = chunk;
    }
};

 

    当内存池不在使用时,析构释放全部内存,此时只需要释放head为首指针的链表的每个结点(遍历删除)。代码如下:

template<>
class SimpleMemPool()
{
    ~SimpleMemPool()
    {  
        while(head)
        {
            MemBlock * t = head;
            head = head->next;
            delete t;
        }
    }
}


    实现中,需要注意的是ObjectChunk的双重身份:当它是自由内存块,还未分配给用户时,它是ObjectChunk的数据结构,包含一个链 表指针,用于串连;当它分配给用户时,它退出了自由内存块链表,它被强制转换为T类型(代码中用了reinterpret_cast操作符号强制转换), 此时便不再有ObjectChunk里的数据,不再需要链表指针。所以ObjectChunk结构的大小,应该大于或等于T类型的大小(出现大于情况,是 因为ObjectChunk最小也必须包含一个指针大小,而T类型却小于一个指针大小)。#pragma pack指令的使用,便是尽量使ObjectChunk的大小符合我们的预期(MemBlock也一样)当用户不再使用T类型对象时,便调用了 Delete(T*),此时,T类型里的数据内容都不再重要了,强制变为ObjectChunk后,把其内容覆盖,使用前几个字节作为指针,又加入自由内 存链表中。

    总体说来,这个简单的内存池,仅仅实现了一次(向系统)申请,多次分配(给用户)的功能。对于大内存块(MemBlock),采取的方法是只申不 放,仅在内存池销毁时才一次性全部释放。这样的策略仅仅适用于内存申请与释放频繁,且内存充足的情况。而多线程,可变大小的情况,也需要更多考虑。

附完整代码:

template<typename T, int BaseSize=32>
class SimpleMemPool
{
private:
#pragma pack(push, 1)
   union ObjectChunk
   {
       ObjectChunk * next;
       char buf[ sizeof(T)];
   }; 
   struct MemBlock
   {
       MemBlock* next;
       ObjectChunk chunks[BaseSize];
   };
#pragma pack(pop)

   SimpleMemPool(const SimpleMemPool<T, BaseSize> &) { } 

     MemBlock * head;
     ObjectChunk * freeChunk;
public:
     SimpleMemPool() : head(0), freeChunk(0)
     {  
     }

     ~SimpleMemPool()
     {  
         while(head)
         {
             MemBlock * t = head;
             head = head->next;
             delete t;
         }
     }

     T* New()
     {
         if (!freeChunk)
         {
             MemBlock * t = new MemBlock;
             t->next = head;
             head = t;
             for(unsigned int i=0; i<BaseSize-1;++i)
             {
                 head->chunks[i].next = & (head->chunks[i+1]);
             }
             head->chunks[BaseSize-1].next = 0;
             freeChunk = & (head->chunks[0]);
         }

         ObjectChunk * t = freeChunk;
         freeChunk = freeChunk->next;
         return reinterpret_cast<T*>(t);
     }

     void Delete(T* t)
     {
         ObjectChunk * chunk = reinterpret_cast<ObjectChunk*>(t);
         chunk->next= freeChunk;
         freeChunk = chunk;
     }
};

 

文章评论

Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员应该关注的一些事儿
程序员应该关注的一些事儿
程序员和编码员之间的区别
程序员和编码员之间的区别
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
我是如何打败拖延症的
我是如何打败拖延症的
为什么程序员都是夜猫子
为什么程序员都是夜猫子
程序员必看的十大电影
程序员必看的十大电影
老程序员的下场
老程序员的下场
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
10个调试和排错的小建议
10个调试和排错的小建议
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
 程序员的样子
程序员的样子
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
那些争议最大的编程观点
那些争议最大的编程观点
Java程序员必看电影
Java程序员必看电影
代码女神横空出世
代码女神横空出世
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
我的丈夫是个程序员
我的丈夫是个程序员
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
鲜为人知的编程真相
鲜为人知的编程真相
每天工作4小时的程序员
每天工作4小时的程序员
旅行,写作,编程
旅行,写作,编程
一个程序员的时间管理
一个程序员的时间管理
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
编程语言是女人
编程语言是女人
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序员都该阅读的书
程序员都该阅读的书
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有