`
waret
  • 浏览: 132666 次
  • 性别: Icon_minigender_1
  • 来自: 天津
文章分类
社区版块
存档分类
最新评论

最简单的内存池-原理与实现

阅读更多

    内存池的主要作用,简单地说来,便是提高内存的使用效率。堆内存的申请与释放(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;
     }
};

 

  • 大小: 5.9 KB
分享到:
评论

相关推荐

    code-with-comments:阅读代码,对其进行注释

    介绍:一个用 C++ 实现的简单内存池 源码分析: 链接: Webbench 介绍:Webbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL, 测试网站在压力下工作的性能,...

    java面试宝典

    31、java 中会存在内存泄漏吗,请简单描述。 11 32、abstract 的method 是否可同时是static,是否可同时是native,是否可同时是synchronized? 11 33、静态变量和实例变量的区别? 11 34、是否可以从一个static 方法...

    Java面试宝典-经典

    43、Java中的异常处理机制的简单原理和应用。 28 44、请写出你最常见到的5个runtime exception。 28 45、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出...

    JAVA面试题最全集

    简单介绍连接池的优点和原理。 5.Web.xml的作用 四、其他 1.Web安全性的考虑(表单验证、浏览器Basic方式的验证,应用程序的安全性,SSL,代码考虑) 2.简单介绍您所了解的MVC。 3.简单介绍所了解的XML。 4....

    千方百计笔试题大全

    31、java 中会存在内存泄漏吗,请简单描述。 11 32、abstract 的method 是否可同时是static,是否可同时是native,是否可同时是synchronized? 11 33、静态变量和实例变量的区别? 11 34、是否可以从一个static 方法...

    java 面试题 总结

    从内存方面来看, Stateful Session Bean 与 Stateless Session Bean 比较, Stateful Session Bean 会消耗 J2EE Server 较多的内存,然而 Stateful Session Bean 的优势却在于他可以维持使用者的状态。 9、...

    JAVA上百实例源码以及开源项目源代码

    2个目标文件 摘要:Java源码,文件操作,TCP,服务器 Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。...

    java基础题 很全面

    39. Java中的异常处理机制的简单原理和应用。 11 40. 垃圾回收的优点和原理。并考虑2种回收机制。 11 41. 你所知道的集合类都有哪些?主要方法? 12 42. 描述一下JVM加载class文件的原理机制? 12 43. char型变量中能不...

    《计算机操作系统》期末复习指导

    优点是实现互斥简单;缺点是效率很低。 •信号量(Semaphore)及PV操作 PV操作能够实现对临界区的管理要求。它由P操作原语和V操作原语组成,对信号量进行操作,具体定义如下: P(S):①将信号量S...

    超级有影响力霸气的Java面试题大全文档

    从内存方面来看, Stateful Session Bean 与 Stateless Session Bean 比较, Stateful Session Bean 会消耗 J2EE Server 较多的内存,然而 Stateful Session Bean 的优势却在于他可以维持使用者的状态。 12、...

    JAVA上百实例源码以及开源项目

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    最新Java面试宝典pdf版

    43、Java中的异常处理机制的简单原理和应用。 28 44、请写出你最常见到的5个runtime exception。 28 45、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出...

    Java面试宝典2010版

    43、Java中的异常处理机制的简单原理和应用。 44、请写出你最常见到的5个runtime exception。 45、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?...

    Java面试笔试资料大全

    43、Java中的异常处理机制的简单原理和应用。 28 44、请写出你最常见到的5个runtime exception。 28 45、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出...

    oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串

    简单来说是本身可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行新增、截取、更新、删除等操作。 常见的数据模型 1. 层次结构模型: 层次结构模型实质上是一种有根结点的定向有序树,IMS...

    java面试题大全(2012版)

    43、Java中的异常处理机制的简单原理和应用。 28 44、请写出你最常见到的5个runtime exception。 28 45、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出...

    JAVA面试宝典2010

    43、Java中的异常处理机制的简单原理和应用。 28 44、请写出你最常见到的5个runtime exception。 28 45、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出...

Global site tag (gtag.js) - Google Analytics