內存池是一種內存分配方式。通常我們習慣直接使用new、malloc等API申請分配內存,這樣做的缺點在於:由於所申請內存塊的大小不定,當頻繁使用時會造成大量的內存碎片。並由於頻繁的分配和回收內存會降低性能,我們都知道,對象的構造和析構都是要花費時間的。
內存池也是一種對象池,我們在使用內存對象之前,先申請分配一定數量的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠再繼續申請新的內存。當不需要此內存時,重新將此內存放入預分配的內存塊中,以待下次利用。這樣合理的分配回收內存使得內存分配效率得到提升。
我們先看一個類。
1 class Rational 2 { 3 public: 4 Rational(int a = 0, int b = 1) :m(a), n(b) 5 { 6 } 7 private: 8 9 int m; 10 int n; 11 };
為了測試頻繁的分配和回收此類對象我們寫一個測試代碼.
1 #include "stdafx.h" 2 #include <iostream> 3 #include <ctime> 4 5 using std::cout; 6 using std::cin; 7 using std::endl; 8 using std::minus; 9 10 #define R_SIZE 1000 11 12 int _tmain(int argc, _TCHAR* argv[]) 13 { 14 Rational *array[R_SIZE]; 15 double ft = .0f; 16 double st = .0f; 17 18 ft = clock(); 19 for (int i = 0; i < 500; ++i) 20 { 21 for (int j = 0; j < R_SIZE; ++j) 22 { 23 array[j] = new Rational; 24 } 25 for (int j = 0; j < R_SIZE; ++j) 26 { 27 delete array[j]; 28 } 29 30 } 31 st = clock(); 32 33 cout << minus<double>()(st, ft) << endl; 34 35 cin.get(); 36 37 return 0; 38 }
這樣我們對Rational對象進行了100萬次的對象分配和回收操作。這個代碼在我的電腦上大概需要4890ms的執行時間
,
這是我們使用默認的內存管理器分配和回收。顯然,如此頻繁的分配以及回收會頻繁的觸發對象構造和析構函數,勢必降低程序的運行。這就是好比我們要裝水,我們沒裝一桶水都要去買水桶,用完了就賣出去,用到的時候再去買。這樣不僅費時費力,而且還降低了我們的裝水的效率。那我們要是買來適量是水桶,我們要用的時候拿出來用,不用的時候放回去,不夠時再去買,這樣省了我們不少的時間。這就是我們的內存池思想。我們現在寫一個專用的Rational內存管理器。
我們聲明一個NextOnFreeList類來連接空閑列表的相鄰對象。
1 class NextOnFreeList 2 { 3 public: 4 NextOnFreeList *next; 5 };
class RationalMemPool { public: RationalMemPool(int a = 0, int b = 1) :m(a), n(b) { } /* * 重載new操作,如果對象鏈表中沒有空閑將重新分配。 * 如果有空閑則從頭部取出一個返回,並把空閑列表下移。 */ inline void *operator new(size_t _sz) { if (freeList == nullptr) { expandTheFreeList(); } NextOnFreeList *head = freeList; freeList = head->next; return head; } /* * 重載delete操作,將對象放回預分配鏈表中 */ inline void operator delete(void* _doomed, size_t _sz) { NextOnFreeList *head = static_cast<NextOnFreeList*>(_doomed); head->next = freeList; freeList = head; } static void newMemPool(){ expandTheFreeList(); } static void deleteMemPool(); private: static void expandTheFreeList(); enum{ EXPANSION = 50 }; //預分配對象數量 int m; int n; static NextOnFreeList *freeList; //空閑鏈表指針 }; NextOnFreeList * RationalMemPool::freeList = nullptr; /* * 使用完銷毀對象 */ void RationalMemPool::deleteMemPool() { NextOnFreeList *nextPtr; for (nextPtr = freeList; nextPtr != nullptr; nextPtr = freeList) { freeList = freeList->next; delete[] nextPtr; } } /* * 事先分配一定數量的對象 */ void RationalMemPool::expandTheFreeList() { size_t size = (sizeof(RationalMemPool) > sizeof(NextOnFreeList*)) ? sizeof(RationalMemPool) : sizeof(NextOnFreeList*); NextOnFreeList *runner = reinterpret_cast<NextOnFreeList *>(new char[size]); freeList = runner; for (int i = 0; i < EXPANSION; ++i) { runner->next = reinterpret_cast<NextOnFreeList *>(new char[size]); runner = runner->next; } runner->next = 0; }
重新設計了Rational的內存分配策略后,我們再來看看測試代碼的運行時間。
1 #include "stdafx.h" 2 #include <iostream> 3 #include <ctime> 4 5 using std::cout; 6 using std::cin; 7 using std::endl; 8 using std::minus; 9 10 #define R_SIZE 1000 11 12 int _tmain(int argc, _TCHAR* argv[]) 13 { 14 double ft = .0f; 15 double st = .0f; 16 RationalMemPool *array[R_SIZE]; 17 18 ft = clock(); 19 RationalMemPool::newMemPool(); 20 for (int i = 0; i < 500; ++i) 21 { 22 for (int j = 0; j < R_SIZE; ++j) 23 { 24 array[j] = new RationalMemPool(j); 25 } 26 for (int j = 0; j < R_SIZE; ++j) 27 { 28 delete array[j]; 29 } 30 } 31 RationalMemPool::deleteMemPool(); 32 st = clock(); 33 34 cout << minus<double>()(st, ft) << endl; 35 36 cin.get(); 37 38 return 0; 39 }
測試代碼在我電腦上運行需要的時間為40ms
我們可以看到運行效率不只是一個級別。
參考《提高C++性能的編程技術》
/********************************2016-11-25更新****************************************/
我們在以上已經封裝了一個專用的內存管理類了。那要是我們想讓一個新類擁有類似的功能是不是也是要為這個類實現上面的成員呢,答案是的,不過我們可以通過一些技巧來完成,比如我們建立一個擁有這些能力的基類,再繼承這個基類以擁有這些功能就可以了。
1 typedef unsigned char UCHAR; 2 3 template <class T, int BLOCK_NUM = 5> 4 class GenericMP 5 { 6 public: 7 static void *operator new(size_t allocLen) 8 { 9 assert(sizeof(T) == allocLen); 10 if (!m_NewPointer) 11 MyAlloc(); 12 UCHAR *rp = m_NewPointer; 13 m_NewPointer = *reinterpret_cast<UCHAR**>(rp); //由於頭4個字節被“強行”解釋為指向下一內存塊的指針,這里m_NewPointer就指向了下一個內存塊,以備下次分配使用。 14 return rp; 15 } 16 static void operator delete(void *dp) 17 { 18 *reinterpret_cast<UCHAR**>(dp) = m_NewPointer; 19 m_NewPointer = static_cast<UCHAR*>(dp); 20 } 21 22 static void deletePool() 23 { 24 UCHAR *p = m_NewPointer; 25 26 delete[] m_NewPointer; 27 m_NewPointer = nullptr; 28 } 29 private: 30 static void MyAlloc() 31 { 32 m_NewPointer = new UCHAR[sizeof(T)* BLOCK_NUM]; 33 34 memset(m_NewPointer, 0, sizeof(T)* BLOCK_NUM); 35 36 cout << sizeof(T)* BLOCK_NUM << endl; 37 UCHAR **cur = reinterpret_cast<UCHAR**>(m_NewPointer); //強制轉型為雙指針,這將改變每個內存塊頭4個字節的含義。 38 UCHAR *next = m_NewPointer; 39 for (size_t i = 0; i < BLOCK_NUM - 1; ++i) 40 { 41 next += sizeof(T); 42 *cur = next; 43 cur = reinterpret_cast<UCHAR**>(next); //這樣,所分配的每個內存塊的頭4個字節就被“強行“解釋為指向下一個內存塊的指針, 即形成了內存塊的鏈表結構。 44 } 45 *cur = 0; 46 } 47 static UCHAR *m_NewPointer; 48 protected: 49 ~GenericMP() 50 { 51 } 52 }; 53 template<class T, int BLOCK_NUM > 54 UCHAR *GenericMP<T, BLOCK_NUM >::m_NewPointer; 55 56 ////////////////////////////////////////////////////////////////////////// 57 class ExpMP : public GenericMP<ExpMP> 58 { 59 public: 60 UCHAR a[10]; 61 }; 62 ////////////////////////////////////////////////////////////////////////// 63 int _tmain(int argc, _TCHAR* argv[]) 64 { 65 ExpMP *aMP1 = new ExpMP(); 66 memcpy_s(aMP1->a + 4, 10, "aMP1", strlen("aMP1")); //由於頭四個字節我們已經用來存放下一個對象的指針了,所以存在數據要從4個字節以后開始 67 68 ExpMP *aMP2 = new ExpMP(); 69 memcpy_s(aMP2->a + 4, 10, "aMP2", strlen("aMP2")); 70 71 delete aMP1; 72 delete aMP1; 73 74 ExpMP::deletePool(); 75 76 77 cin.get(); 78 79 return 0; 80 }
