1. 程式人生 > >記憶體池的原理及實現

記憶體池的原理及實現

在軟體開發中,有些物件使用非常頻繁,那麼我們可以預先在堆中例項化一些物件,我們把維護這些物件的結構叫“記憶體池”。在需要用的時候,直接從記憶體池中拿,而不用從新例項化,在要銷燬的時候,不是直接free/delete,而是返還給記憶體池。

把那些常用的物件存在記憶體池中,就不用頻繁的分配/回收記憶體,可以相對減少記憶體碎片,更重要的是例項化這樣的物件更快,回收也更快。當記憶體池中的物件不夠用的時候就擴容。

我的記憶體池實現如下:

#pragma once
#include <assert.h>

template<typename T>
struct ProxyT
{ 
    ProxyT():next(NULL){} 
    T data;
    ProxyT
* next; }; template<typename T> class MemoryPool { public: static void* New() { if(next==NULL) { Alloc(); } assert(next!=NULL); ProxyT<T>* cur=next; next=next->next; return cur; }
static void Delete(void* ptr) { ProxyT<T>* cur=static_cast<ProxyT<T>*>(ptr); cur->next=next; next=cur; } #ifdef CanFree static void Clear() { ProxyT<T>* proxy=NULL; while(next!=NULL) { proxy
=next->next; delete next; next=proxy->next; } next=NULL; } #endif private: static void Alloc(size_t size=16) { if(next==NULL) { #ifdef CanFree ProxyT<T>* tmpProxy=new ProxyT<T>(); next=tmpProxy; for(int i=1;i<size;i++) { tmpProxy->next=new ProxyT<T>(); tmpProxy=tmpProxy->next; } #else ProxyT<T>* memory=(ProxyT<T>*)malloc(size*sizeof(ProxyT<T>)); ProxyT<T>* tmpProxy=new (memory) ProxyT<T>(); next=tmpProxy; for (size_t i=1;i<size;i++) { tmpProxy->next=new (memory+i) ProxyT<T>(); tmpProxy=tmpProxy->next; } #endif } } static ProxyT<T>* next; MemoryPool<T>(); MemoryPool<T>(const MemoryPool<T>&); }; template<typename T> ProxyT<T>* MemoryPool<T>::next=NULL; #define NewAndDelete(className) \ static void* operator new(size_t size) \ { \ return MemoryPool<className>::New(); \ } \ static void operator delete(void* ptr) \ { \ MemoryPool<className>::Delete(ptr); \ }

測試程式碼如下:

#include "stdafx.h" 
#define CanFree
#include "MemoryPool.h"
 
struct A
{ 
    int i; 
    NewAndDelete(A) 
};
  
int _tmain(int argc, _TCHAR* argv[])
{   
     
    { 
        vector<A*> vect;
        for(int i=0;i<16;i++)
        {
            A* a=new A();
            a->i=i;
            vect.push_back(a);
        }
        for(int i=0;i<vect.size();i++)
        {
            cout<<vect[i]->i<<endl;
        }
        for(int i=vect.size()-1;i>=0;i--)
        {
            delete vect[i];
        }
        vect.clear();
        
        MemoryPool<A>::Clear();
    }
   
    system("pause");
    return 0; 
}


執行結果如下圖:

不到100行程式碼,有兩個public方法New和Delete;還有一個Clear方法,這個方法的存在取決於是否定義了巨集CanFree,如果定義了這個巨集,那麼物件是一個個的例項化,在呼叫Clear的時候可以一個個的回收,如果沒有定義,那麼是一次分配一塊較大的記憶體,然後在這塊記憶體上例項化多個物件,但沒有實現回收這塊記憶體的方法,如果要回收這樣的大塊記憶體塊,就必須將這些記憶體塊的首地址存起來,我這裡沒有存起來,而且還要標記物件是否使用,那麼Proxy<T>還要加一個欄位表示是否使用,在回收的時候還要判斷所有物件是否沒有使用,只有都沒使用才能回收,妹的,為了回收弄得這麼麻煩,話說你為什麼要回收記憶體池呢,於是就沒有實現回收的方法。整個記憶體池其實就是一個單鏈表,表頭指向第一個沒有使用節點,我們可以把這個單鏈表想象成一段鏈條,呼叫方法New就是從鏈條的一端(單鏈表表頭)取走一節點,呼叫方法Delete就是在鏈條的一端(單鏈表表頭)前面插入一個節點,新插入的節點就是連結串列的表頭,這樣New和Delete的時間複雜度都是O(1),那叫一個快。

所有要使用記憶體池的物件,只需要在這個物件中引入巨集NewAndDelete,這個巨集其實就是重寫物件的new和delete方法,讓物件的建立和回收都通過記憶體池來實現,所有用記憶體池實現的物件使用起來和別的物件基本上是一樣,唯一的一個問題就是記憶體池物件物件不是執行緒安全的,在多執行緒程式設計中,建立一個物件時必須枷鎖。如果在New和Delete的實現中都加個鎖,我又覺得他太影響效能,畢竟很多時候是不需要枷鎖,有些物件可能有不用於多執行緒,對於這個問題,求高手指點!