C++内存池初探
原创文章,转载请注明出处:http://blog.csdn.net/fastsort/article/details/12356369
///mymem.h 使用自己定义的内存管理需要包含这个头文件
#ifndef __mymem_h__ #define __mymem_h__ template <class T> class CachedObj { public: void *operator new(std::size_t); void operator delete(void *, std::size_t); virtual ~CachedObj(){} protected: T * next; private: static void AddToFreeList(T *); static std::allocator<T> allocMem; static T * freeStore; static const std::size_t chunk; }; ////////////////////////////////////////////////////// ///函数实现和数据初始化 ///模板中将头文件和实现文件分离在vs中有问题 ////////////////////////////////////////////////////// template <class T> void *CachedObj<T>::operator new(size_t sz) { //cout<<"op new"<<endl; if(sz != sizeof(T))///在vs下这里没问题,在Linux下这里需要修改一下 throw std::runtime_error ("CachedObj : wrong size object in op new. "); if(!freeStore) { T * a = allocMem.allocate(chunk); for(size_t i=0; i!= chunk; i++) AddToFreeList(&a[i]); } T * p = freeStore; freeStore = freeStore->CachedObj<T>::next; return p; } template <class T> void CachedObj<T>::operator delete(void *p, size_t sz) { //cout<<"op delete"<<endl; if(p) AddToFreeList(static_cast<T*>(p)); } template <class T> void CachedObj<T>::AddToFreeList(T * p) { p->CachedObj<T>::next = freeStore; freeStore = p; } template <class T> std::allocator< T > CachedObj<T>::allocMem ; template <class T> T * CachedObj<T>::freeStore = 0 ; template <class T> const size_t CachedObj<T>::chunk = 20 ; #endif //#endif __mymem_h__
由于使用了模板,如果把头文件和实现代码分离,编译出错。
在网上也查询了一些解决办法,但是都有局限性,不同编译器的支持度也不一样,所以干脆实现代码也放在头文件里了。
//// main.cpp 测试用的主函数
#include <iostream> #include <time.h> #include <fstream> #include "mymem.h" ///自己定义的内存管理基类 using namespace std; ofstream ofs("perfRec.txt",ios::app); /***优化S类的内存管理 **/ class S: public CachedObj<S> { public : S() { } void fun() { cout<<"fun is called!"<<endl; } ~S() { } }; /**不优化T的内存管理**/ class T { public : T() { } void fun() { cout<<"fun is called!"<<endl; } ~T() { } }; /**记录测试结果的函数**/ void rec(int cnt, int sd, int td) { ofs<<cnt<<"\t\t"; ///count ofs<<sd<<"\t\t"<<td<<"\t\t";//s , d if(sd) ofs<< ((int)(1.0*td/sd*10)/10.0) <<endl;///加速比,保留一位小数 else ofs<< "-" <<endl; ofs.flush(); } /**申请完立即释放测试**/ void testA(int n=1024000) { cout<< __FUNCTION__ <<" count = "<< n <<endl; //int CTest = n; clock_t start,end; start = clock(); for(int i=0; i<n; i++) { S *ps = new S(); delete ps; } end = clock(); int sd = end-start; cout<<"S duratuon : "<< sd <<"ms"<<endl; start = clock(); for(int i=0; i<n; i++) { T *ps = new T(); delete ps; } end = clock(); int td = end-start; cout<<"T duration : "<< td <<"ms"<<endl; rec(n,sd,td); } /**申请完不立即释放测试***/ void testB(int n=1024000) { cout<< __FUNCTION__ <<" count = "<< n <<endl; S ** psa = new S*[n] ; clock_t start,end; start = clock(); for(int i=0; i<n; i++) { psa[i] = new S(); } for(int i=n-1; i>=0; i--) { delete psa[i]; } end = clock(); int sd = end-start; cout<<"S duratuon : "<< sd <<"ms"<<endl; T ** pta = new T*[n]; start = clock(); for(int i=0; i<n; i++) { pta[i] = new T(); } for(int i=n-1; i>=0; i--) { delete pta[i]; } end = clock(); int td = end-start; cout<<"T duration : "<< td <<"ms"<<endl; rec(n,sd,td); delete []psa; delete []pta; } void fa(int n) { int i,s; ofs<<"===================Table a==================="<<endl; ofs<<"!!! release after per alloc at once !!!"<<endl; ofs<<"count"<<"\t\t"<<"s duration(ms)"<<"\t\t"<<"t duration(ms)"<<endl; for(i=0,s=1000; i<n; i++) { s *= 10; testA(s); } //testA(s*5); } void fb(int n) { int i,s; ofs<<"===================Table B==================="<<endl; ofs<<"!!! release after alloc all elements !!!"<<endl; ofs<<"count"<<"\t\t"<<"s duration(ms)"<<"\t\t"<<"t duration(ms)"<<endl; for(i=0,s=1000; i<n; i++) { s *= 10; testB(s); } //testB(s*5); } int main() { ofs<<"========performance test in win7(x64)========"<<endl; int n=4; int cnt=10; while(cnt--) { fa(n); fb(n); } ofs<<"======test end=========\n\n\n\n\n\n\n"<<endl; return 0; }
如果要对某个类进行手动内存管理(或者说用我们自己定义的内存池),只需要让该类继承CachedObj类即可。
需要注意的是,CachedObj是一个模板,继承应该这样写:
class S: public CachedObj<S> { ... };
为了对比,同时定义了另外一个类T,使用系统的内存管理策略。
测试结果:
(第一列为申请/释放内存次数,第二列为S的结果,第三列为T的结果,最后一列是T/S的值)
在win7(x64)下:
===================Table a=================== !!! release after per alloc at once !!! count s duration(ms) t duration(ms) 10000 0 1 - 100000 1 10 10 1000000 8 96 12 10000000 71 977 13.7 ===================Table B=================== !!! release after alloc all elements !!! count s duration(ms) t duration(ms) 10000 0 1 - 100000 2 12 6 1000000 12 115 9.5 10000000 123 1185 9.6
效果十分明显,效率提升约十倍!
但是代码放到Linux下,就不是那么明显了:
========performance test in ubuntu ======== ===================Table a=================== !!! release after per alloc at once !!! count s duration(m) t duration(ms) 10000 0 0 - 100000 10000 10000 1 1000000 50000 60000 1.2 10000000 530000 560000 1 50000000 2640000 2860000 1 ===================Table B=================== !!! release after alloc all elements !!! count s duration(m) t duration(ms) 10000 0 0 - 100000 10000 10000 1 1000000 70000 80000 1.1 10000000 670000 830000 1.2 50000000 3250000 4130000 1.2
不得不说,Linux比win的内存管理貌似牛x很多。
继续测试,将chunk修改为每次增大一倍,直到不能增大,但是效果还是不明显,仍然在1附近。
后来无意中想起优化选项,
于是编译时添加-O0和-O3,对比其结果有什么不同。
开始很奇怪的是,-O3不仅没有提高效率,反而降低了效率。
但是后来分析发现,只是加速比增加的很明显,而实际上,绝对速度都比未优化的要快:
使用-O0和-O3优化选项的结果对比图。
由此看出,使用自己的内存池管理内存还是有必要的。
遗留问题:
申请的内存始终没有返回给系统。
如果系统出现某个很高的峰值后,一直处在较低的负载下运行,则有大量的内存被本程序占用且未被使用。
一个解决办法是每隔一段时间,看内存池空闲的的比例是多少,如果很高,则释放一部分返回给系统。
附:整个测试代码和结果
http://download.csdn.net/detail/hello_world_2012/6360453
参考:《C++ primer 第四版》
原创文章,转载请注明出处: blog.csdn.net/fastsort/article/details/12356369