c++内存池

基本原理:一次申请多个对象的内存比多次申请一个对象的内存要快。内存池正式基于这个简单的道理。内存池先从系统中申请一块能容纳多个对象的内存块,以后每次申请一个对象都从内存池中获得;释放一个对象时内存归还内存池。

第一种是从《Effective C++》中看到的写法:

template<typename T>
class object_pool
{
public:
    static void* operator new(size_t size);
    static void operator delete(void* p, size_t size);
    virtual ~object_pool(){};
private:
    static void add_to_free_list(T *p);
    static void free_from_pool(T *p);
protected:
    T *_next;
private:
    static T *_next_free;
    static size_t _size;
};
template<typename T>
T* object_pool<T>::_next_free = nullptr;

template<typename T>
size_t object_pool<T>::_size = 80;

template<typename T>
void object_pool<T>::add_to_free_list(T* p)
{
    p->_next = _next_free;
    _next_free = p;
}

template<typename T>
void object_pool<T>::free_from_pool(T *p)
{

}

template<typename T>
void object_pool<T>::operator delete(void *p, size_t size)
{
    if(p == nullptr) 
    {
        return ;
    }
    if(sizeof(T) != size)
    {
        ::operator delete(p);
        return ;
    }
    free_from_pool(static_cast<T*>(p));
    add_to_free_list(static_cast<T*>(p));
}

template<typename T>
void* object_pool<T>::operator new(size_t size)
{
    if(sizeof(T) != size)
    {
        return ::operator new(size);
    }
    if(_next_free == nullptr)
    {
        T *new_block = static_cast<T*>(::operator new(sizeof(T)*_size));
        for(size_t i = 0; i < _size; ++i)
        {
            add_to_free_list(&new_block[i]);
        }
        new_block[_size - 1]._next = nullptr;
    }
    T *res = _next_free;
    _next_free = res->_next;
    return res;
}

这种内存池使用模板,能够适用于任何类。通过重载自身的静态operator new和operator delete操作,很自然的将对象内存分配过程接管到自己的代码中。使用MixIn方式继承,例如:

class Person:public object_pool<Person>
{
    ...
};

使用非常方便,但是效率并不优秀,只比系统提供的new和delete操作快了大约10%。至于原因,我水平有限,只能猜测。大概是因为继承的关系,每次申请一个新对象需要调用父类的operator new,每次释放要给对象需要调用父类的operator delete。即使什么都不做,比常规new和delete操作慢上约16%。但这不像是效率不高的主要原因,还请各位高手指点。

第二种从一位大神博客里看到,这篇博客地址为:http://www.cppblog.com/weiym/archive/2012/05/05/173785.aspx。下面是他博客中的代码(略有修改):

template<typename T>
class CMemoryPool
{
    public:
        const static int EXPANSION_SIZE = 32;

        CMemoryPool(unsigned int nItemCount = EXPANSION_SIZE)
        {
            ExpandFreeList(nItemCount);
        }

        ~CMemoryPool()
        {
            CMemoryPool<T>* pNext = NULL;
            for(pNext = m_pFreeList; pNext != NULL; pNext = m_pFreeList)
            {
                m_pFreeList = m_pFreeList->m_pFreeList;
                delete [](char*)pNext;
            }
        }

        void* Alloc(unsigned int)
        {
            if(m_pFreeList == NULL)
            {
                ExpandFreeList();
            }

            CMemoryPool<T>* pHead = m_pFreeList;
            m_pFreeList = m_pFreeList->m_pFreeList;
            return pHead;
        }

        void Free(void* p)
        {
            CMemoryPool<T>* pHead = static_cast<CMemoryPool<T>*>(p);
            pHead->m_pFreeList = m_pFreeList;
            m_pFreeList = pHead;
        }

    protected:
        void ExpandFreeList(unsigned nItemCount = EXPANSION_SIZE)
        {
            unsigned int nSize = sizeof(T) > sizeof(CMemoryPool<T>*) ? sizeof(T) : sizeof(CMemoryPool<T>*);
            CMemoryPool<T>* pLastItem = reinterpret_cast<CMemoryPool<T>*>(new char[nSize]);
            m_pFreeList = pLastItem;
            for(int i=0; i<nItemCount-1; ++i)
            {
                pLastItem->m_pFreeList = reinterpret_cast<CMemoryPool<T>*>(new char[nSize]);
                pLastItem = pLastItem->m_pFreeList;
            }

            pLastItem->m_pFreeList = NULL;
        }

    private:
        CMemoryPool<T>* m_pFreeList;
};

每次pool向系统申请固定数量(nItemCount)的内存单元,组织成空闲链表。虽然使用不及第一种方便,但是效率远比系统的new和delete高。
在ExpandFreeList函数中,第一句保证一个单元的空间能够容纳指针。其思想是:如果当前是空闲块,那么就用里面的空间保存指向下一个空闲单元的指针,可以节省空间;如果当前不是空闲块,那么事先已经把下一个空闲单元的指针保留给空闲链表头指针了,此时指针雨也没用了。所以是一种节约内存的写法。
使用例子:

class CTest
{
public:
    int m_n;
    int m_n1;

    void* operator new(size_t size)
    {
        void* p = s_pool->Alloc(size);
        return p;
    }

    void operator delete(void* p, size_t size)
    {
        s_pool->Free(p);
    }

    static void NewPool()
    {
        s_pool = new CMemoryPool<CTest>;
    }

    static void DeletePool()
    {
        delete s_pool;
        s_pool = NULL;
    }

    static CMemoryPool<CTest>* s_pool;
};

CMemoryPool<CTest>* CTest::s_pool = NULL;

第三种写法来自:http://cplusplus.wikidot.com/cn:dive-into-memory-pool
里面介绍了另一种空闲链表写法,同时对比boost中pool的实现,将每次向系统申请固定数量内存单元改进成每次申请前一次两倍数量的内存单元,类似于STL中vector的内存模型。代码如下:

#ifndef __POOL__
#define __POOL__

#include <cstdint>

template<typename T, uint32_t itemSize = sizeof(T)>
class Pool
{
private:
    uint32_t    blockSize;

    struct _FreeNode 
    {
        union
        {
            _FreeNode   *prePtr;
            uint8_t     data[itemSize];
        };
        explicit _FreeNode():prePtr(nullptr)
        {

        }
    };

    struct _MemBlock
    {
        _MemBlock   *prePtr;
        _FreeNode   *data;

        explicit _MemBlock(_MemBlock *pre):prePtr(pre), data(nullptr)
        {

        }
    };

    _MemBlock   *blockHeader;
    _FreeNode   *freeNodeHeader;

public:
    Pool(uint32_t bsize = 32) : blockSize(bsize),
        blockHeader(nullptr), 
        freeNodeHeader(nullptr)
    {

    }

    ~Pool()
    {
        for(_MemBlock *it = blockHeader; it != nullptr; it = it->prePtr)
        {
            delete []it->data;
            it->data = nullptr;
        }
        for(_MemBlock *it = blockHeader, *pre = it; it != nullptr; it = pre)
        {
            pre = it->prePtr;
            delete it;
        }
    }

    void* Malloc()
    {
        if(freeNodeHeader == nullptr)
        {
            _MemBlock *block = new _MemBlock(blockHeader);
            block->data = new _FreeNode[blockSize];
            for(int i = 0; i < blockSize; ++i)
            {
                block->data[i].prePtr = freeNodeHeader;
                freeNodeHeader = &block->data[i];
            }
            blockHeader = block;
            blockSize <<= 1;
        }
        void *ret = freeNodeHeader;
        freeNodeHeader = freeNodeHeader->prePtr;
        return ret;
    }

    void Free(void *ptr)
    {
        _FreeNode *temp = reinterpret_cast<_FreeNode*>(ptr);
        temp->prePtr = freeNodeHeader;
        freeNodeHeader = temp;
    }
};

#endif

对内存单元做出略微修改,用联合体保证空闲单元有足够空间,省去判断。这种写法地第二种略快,但使用会方便一点,例如:

class Point
{
    ...
};

Pool<Point> pool;
Point *ptr = new (pool.Malloc())Point;

PS:其实第二种也可以这样用。

第四种写比较复杂,用到STL的map和vector,实现gc功能。参见参考第四条。

参考:
《Effective C++》
http://www.cppblog.com/weiym/archive/2012/05/05/173785.aspx
http://cplusplus.wikidot.com/cn:dive-into-memory-pool
http://blog.sina.com.cn/s/blog_54384df801019ahp.html

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。