c/c++:efficient c++,单线程内存池
c++ efficient 的第六章,看书笔记,顺便说下理解。
对于一般直接 new 与delete 性能较差,可以自己管理写内存的申请与释放。
版本0:
class Rational { public: Rational(int a=0, int b =1 ): n(a),d(b){} private: int n; int d; };
版本1: 专用的内存管理器
这版本是通过重载 目标类 中的new 与delete 实现内存管理,只适用于目标类,但是速度是最快的。
实现方式是维护一个链表,类中静态声明链表头,链表维护一串空间,通过类型转换在 目标类 和 链表指针 之间转换。
如果内存不够(freelist=NULL)是一次申请较多内存进行维护。
链表的添加删除(对于函数释放空间与申请空间)是在链首操作。
书上47 行 52行通过 静态类型转换不成功,改为了 强制转换,在codeblock g++ 中能够编译运行。
主要在主函数中调用静态函数 链表的申请与释放。即74 86行。
注意65 行把原来的delete [] 改为了 delete[] (char*) 原因可以参考第二版本。
1 class NextOnFreeList 2 { 3 public: 4 NextOnFreeList *next; 5 }; 6 7 class Rational 8 { 9 public: 10 Rational(int a=0, int b =1 ): n(a),d(b){} 11 inline void *operator new(size_t size); 12 inline void operator delete(void * doomed, size_t size); 13 14 static void newMemPool(){ expandTheFreeList(); } 15 static void deleteMemPool(); 16 static void expandTheFreeList(); 17 private: 18 int n; 19 int d; 20 static NextOnFreeList * freelist; 21 enum{ EXPANSION_SIZE = 32 }; 22 }; 23 24 inline 25 void * Rational::operator new( size_t size) 26 { 27 if(0 == freelist) 28 { 29 expandTheFreeList(); 30 } 31 NextOnFreeList * head =freelist; 32 freelist=head->next; 33 return head; 34 } 35 36 inline 37 void Rational::operator delete(void * doomed ,size_t size) 38 { 39 NextOnFreeList *head=static_cast<NextOnFreeList *>( doomed); 40 head->next=freelist; 41 freelist=head; 42 } 43 44 void Rational:: expandTheFreeList() 45 { 46 size_t size= (sizeof(Rational)>sizeof(NextOnFreeList*))?sizeof(Rational):sizeof(NextOnFreeList*); 47 // NextOnFreeList *runner= static_cast<NextOnFreeList *>(new char [size]); 48 NextOnFreeList *runner= (NextOnFreeList *)(new char [size]); 49 freelist=runner; 50 for(int i=0;i<EXPANSION_SIZE;i++) 51 { 52 // runner->next= static_cast<NextOnFreeList *>( new char [size] ); 53 runner->next= (NextOnFreeList *) new char [size] ; 54 runner=runner->next; 55 } 56 runner->next= 0; 57 } 58 59 void Rational::deleteMemPool() 60 { 61 NextOnFreeList * nextPtr; 62 for(nextPtr=freelist;nextPtr!=NULL;nextPtr=freelist) 63 { 64 freelist=freelist->next; 65 delete [](char *)nextPtr; 66 } 67 } 68 69 NextOnFreeList * Rational::freelist =0; 70 71 int main() 72 { 73 Rational * array[1000]; 74 Rational::newMemPool(); 75 for(int j=0;j<500;j++) 76 { 77 for(int i=0;i<1000;i++) 78 { 79 array[i]=new Rational(i); 80 } 81 for(int i=0;i<1000;i++) 82 { 83 delete array[i]; 84 } 85 } 86 Rational::deleteMemPool(); 87 return 0; 88 }
版本2:固定大小对象的内存池
考虑版本1,可以通过模板实现管理特定的对象。最主要的参数是类的大小。
实现是专门声明一个管理内存的内存池类,使用模板实现,提供alloc() free() 接口,给类申请与释放内存。
内存池类的实现是自身有一个MemoryPool<T>* next 指针用来指向维护的链表,内存的操作都在链首位置。
第32行书上代码编译不通过,
delete [] nextPrt;
时,nextPrt 的类型为 MemoryPool * ,这样在释放的时候再一次调用了 ~MemoryPool 而不是默认的内存释放。(第一次调用是上层类的析构函数调用的)
所以在这改为了
delete [](char*)nextPrt;
这样才能成功释放内存。
下面的是底层类的代码。
1 template<class T> 2 class MemoryPool 3 { 4 public: 5 MemoryPool(size_t size =EXPANSION_SIZE); 6 ~MemoryPool(); 7 8 //从空闲列表中分配T元素 9 inline void * alloc(size_t size); 10 inline void free(void * someElement); 11 private: 12 MemoryPool<T>* next; 13 enum{EXPANSION_SIZE=2}; 14 //将空闲元素添加至空闲列表 15 void expanTheFreeList(int howMany=EXPANSION_SIZE); 16 }; 17 18 template<class T> 19 MemoryPool<T>::MemoryPool(size_t size) 20 { 21 expanTheFreeList( size); 22 } 23 24 template<class T> 25 MemoryPool<T>::~MemoryPool() 26 { 27 cout<<next<<endl; 28 MemoryPool<T> * nextPrt=next; 29 for(nextPrt=next;nextPrt!=NULL;nextPrt=next) 30 { 31 next=next->next; 32 // delete [] nextPrt; 33 delete [](char*)nextPrt; 34 } 35 } 36 37 template<class T> 38 inline 39 void * MemoryPool<T>::alloc(size_t size) 40 { 41 if(!next) 42 { 43 expanTheFreeList(); 44 } 45 MemoryPool<T> * head=next; 46 next=head->next; 47 return head; 48 } 49 50 template<class T> 51 inline 52 void MemoryPool<T>::free(void * doomed) 53 { 54 // MemoryPool<T> *head=static_cast<MemoryPool<T>* >(doomed); 55 MemoryPool<T> *head=(MemoryPool<T>* )(doomed); 56 head->next=next; 57 next=head; 58 } 59 60 template<class T> 61 void MemoryPool<T>::expanTheFreeList(int howmany) 62 { 63 size_t size=(sizeof(T)>sizeof(MemoryPool<T>*))? sizeof(T):sizeof(MemoryPool<T>*); 64 // MemoryPool<T>* runner =static_cast< MemoryPool<T>* >(new char [size]); 65 MemoryPool<T>* runner =( MemoryPool<T>* )(new char [size]); 66 next=runner; 67 for(int i=0 ;i<howmany;i++) 68 { 69 // runner->next=static_cast< MemoryPool<T>* >(new char [size]); 70 runner->next=( MemoryPool<T>* )(new char [size]); 71 runner=runner->next; 72 } 73 runner->next=0; 74 }
下面是调用类与使用的代码:
12行的时候会调用 MemoryPool 的构造函数。
1 //使用内存池的类 2 class Rational 3 { 4 private: 5 int n; 6 int d; 7 static MemoryPool<Rational > *memPool; 8 public: 9 Rational(int a=0, int b=1) : n(a),d(b){} 10 void *operator new(size_t size){ return memPool->alloc(size); } 11 void operator delete(void* doomed, size_t size){ memPool->free(doomed); } 12 static void newMemPool(){ memPool=new MemoryPool <Rational >; } 13 static void deleteMemPool(){ delete memPool; } 14 }; 15 16 MemoryPool<Rational>* Rational::memPool=0; 17 18 int main() 19 { 20 //... 21 Rational::newMemPool(); 22 //... 23 Rational::deleteMemPool(); 24 //.. 25 return 0; 26 }
版本3:将版本2的 固定长度改为不定长度,称为可变大小内存池
首先要有个认识,既然是可变长度,那么通常释放后,再一次申请的时候不会申请这一段内存的,(通过代码的实现是不打算复用内存的)第一是不适用,第二就算够那么将造成管理的困难
想想申请而来123连续的3段内存,第二段内存需要释放,这大小就固定下来了(13还没释放),如果想复用,这个就另外实现吧。 - -!
实现方式是在内存池(版本2 的MemoryPool)下层在创建一个类,用来指向一个大内存块,一个计数,表示前多少内存已经使用了,如果不够就再一次申请一个内存块。至于浪费的考虑,看书上说的吧。 - -!
MemoryPool 还是第二版本的功能,提供接口,不过对于物理内存的申请就下放了。
仍然是维护链表实现,还是链首操作。
内存块 类:
因为最低成的释放free 基于设计因素什么也不赶,要想的也可以对free 修改,参数什么的都有传递。
29行书上是直接 delete [] mem; 不过这样会提示void* 操作,强制转换了
1 class MemoryChunk 2 { 3 private: 4 MemoryChunk* next;//指向下个内存块 5 void * mem;//指向可用的内存 6 size_t chunkSize;//该内存块的大小 7 size_t bytesAlreadyAllocated;//已经分配的字节数 8 public: 9 MemoryChunk(MemoryChunk *nextChunk ,size_t chunkSize); 10 ~MemoryChunk(); 11 12 inline void* alloc(size_t size); 13 inline void free (void* someElement); 14 MemoryChunk* nextMemChunk() { return next; } 15 size_t spaceAvailable() { return chunkSize - bytesAlreadyAllocated; } 16 enum{ DEFUALT_CHUNK_SIZE = 4096 }; 17 }; 18 19 MemoryChunk::MemoryChunk(MemoryChunk* nextChunk, size_t reqSize) 20 { 21 chunkSize = (reqSize > DEFUALT_CHUNK_SIZE) ? reqSize : DEFUALT_CHUNK_SIZE; 22 next = nextChunk; 23 bytesAlreadyAllocated=0; 24 mem= new char [chunkSize]; 25 } 26 27 MemoryChunk::~MemoryChunk() 28 { 29 delete [] (char*)mem; 30 } 31 32 void* MemoryChunk::alloc(size_t requestSize) 33 { 34 void* addr = (void* )((size_t )mem + bytesAlreadyAllocated ); 35 bytesAlreadyAllocated+=requestSize; 36 return addr; 37 } 38 39 inline void MemoryChunk::free(void* someElement ) {}
内存池类:
必须注意16行,对链表的末尾赋0,书上是没有的,这句没有会导致析构的时候不能判断结束。
第26行的时候是调用memChunk的析构函数的。
1 //内存池类 2 class ByteMemoryPool 3 { 4 private: 5 MemoryChunk* listOfMemoryChunks; 6 void expandStorage(size_t reqSize); 7 public: 8 ByteMemoryPool(size_t initSize = MemoryChunk::DEFUALT_CHUNK_SIZE); 9 ~ByteMemoryPool(); 10 inline void* alloc(size_t size); 11 inline void free(void* someElement); 12 }; 13 14 ByteMemoryPool::ByteMemoryPool(size_t initSize) 15 { 16 listOfMemoryChunks=NULL; 17 expandStorage(initSize); 18 } 19 20 ByteMemoryPool::~ByteMemoryPool() 21 { 22 MemoryChunk* memChunk = listOfMemoryChunks; 23 while(memChunk) 24 { 25 listOfMemoryChunks = memChunk->nextMemChunk(); 26 delete memChunk; 27 memChunk = listOfMemoryChunks; 28 } 29 } 30 31 inline 32 void* ByteMemoryPool::alloc(size_t requestSize) 33 { 34 size_t space = listOfMemoryChunks->spaceAvailable(); 35 if(space<requestSize) 36 { 37 expandStorage(requestSize); 38 } 39 return listOfMemoryChunks->alloc(requestSize); 40 } 41 42 inline 43 void ByteMemoryPool::free(void* doomed) 44 { 45 listOfMemoryChunks->free(doomed); 46 } 47 48 void ByteMemoryPool::expandStorage(size_t requestSize) 49 { 50 listOfMemoryChunks = new MemoryChunk(listOfMemoryChunks,requestSize); 51 }
调用类与测试:
21 、33对应创建内存池与释放内存池,是同一个类中共享内存池。
1 //调用类 2 class Rational 3 { 4 private: 5 int n; 6 int d; 7 static ByteMemoryPool* memPool; 8 public: 9 Rational(int a=0, int b=1):n(a),d(b){} 10 void* operator new (size_t size){ return memPool->alloc(size); } 11 void operator delete (void* doomed, size_t size){ memPool->free(doomed); } 12 static void newMemPool(){ memPool = new ByteMemoryPool; } 13 static void deleteMemPool(){ delete memPool; } 14 }; 15 16 ByteMemoryPool* Rational::memPool=0; 17 18 int main() 19 { 20 Rational * array[1000]; 21 Rational::newMemPool(); 22 for(int j=0;j<500;j++) 23 { 24 for(int i=0;i<1000;i++) 25 { 26 array[i]=new Rational(i); 27 } 28 for(int i=0;i<1000;i++) 29 { 30 delete array[i]; 31 } 32 } 33 Rational::deleteMemPool(); 34 return 0; 35 }
总结:
1.灵活性以速度为代价。
2.全局内存管理器是通用的(new 和 delete),因此代价高。
3.专用内存管理器比全局内存管理器快一个数量级以上。
4.如果主要分配固定大小的内存块,专用的固定大小内存管理器将明显地提升性能。
5.如果主要分配限于单线程内存块,那么内存块管理器也会有类似的性能提高。