您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > C/C++/C#

C++内存池实现

时间:2019-08-28 14:42:21  来源:  作者:

内存池是自己向OS请求的一大块内存,自己进行管理。

##系统调用## 我们先测试系统调用new/delete的用时。

#include <IOStream>

#include <time.h> 
using namespace std;
class TestClass
{
private:
 char m_chBuf[4096];
};
timespec diff(timespec start, timespec end)
{
 timespec temp;
 temp.tv_sec = end.tv_sec-start.tv_sec;
 temp.tv_nsec = end.tv_nsec-start.tv_nsec;
 return temp;
}
int main()
{
 timespec time1, time2;
 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1);
 for(unsigned int i=0; i< 0x5fffff; i++)
 {
 TestClass *p = new TestClass;
 delete p;
 }
 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time2);
 cout<<diff(time1,time2).tv_sec<<":"<<diff(time1,time2).tv_nsec<<endl;
}

用时为604124400ns。系统的new是在堆上分配资源,每次执行都会分配然后销毁。

##简单的内存池##

#include <iostream>
#include <time.h> 
using namespace std;
char buf[4100]; //已分配内存
class TestClass
{
public:
 void* operator new(size_t)
 {return (void*)buf;}
 void operator delete(void* p){}
private:
 char m_chBuf[4096];
};
int main()
{
 timespec time1, time2;
 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1);
 for(unsigned int i=0; i< 0x5fffff; i++)
 {
 TestClass *p = new TestClass;
 delete p;
 }
 clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time2);
 cout<< diff(time1,time2).tv_sec<<":"<<diff(time1,time2).tv_nsec<< endl;
}

用时为39420791ns,后者比前者快20倍。

简单内存池在开始在全局/静态存储区分配资源,一直存在。每次重载的new调用只是返回了buf的地址,所以快。

##MemPool定义##

class CMemPool
{
private:
	struct _Unit
	{
		struct _Unit *pPrev, *pNext;
	};
	void* m_pMemBlock;
 struct _Unit* m_pFreeMemBlock;
	struct _Unit* m_pAllocatedMemBlock;
	
	unsigned long m_ulUnitSize; //一个单元的内存大小
	unsigned long m_ulBlockSize; //整个内存池的内存大小
public:
	CMemPool(unsigned long lUnitNum = 50, unsigned long lUnitSize = 1024);
	~CMemPool();
	void* Alloc(unsigned long ulSize, bool bUseMemPool = true);
	void Free(void* p);
};

CMemPool定义了一个_Unit来管理链表,指针被包含在整个结构中,这种方式和内核中的链表写法很像。

m_pMemBlock指向分配的那块大小为m_ulBlockSize的大内存的地址。m_pMemBlock是线性的内存,我们把它用下列这种方式管理。

C++内存池实现

 

它被均分为lUnitNum个大小为m_ulUnitSize Byte的小内存块。每个块分为2部分:Unit链表管理头真正进行存储的内存单元

从图中可以看出m_ulBlockSize的计算方式为:

UnitNum * ( UnitSize + sizeof(Struct _Unit))

然后用双向链表连接所有的小块。m_pFreeMemBlock指向空闲的内存的起始位置,m_pAllocatedMemBlock指向已分配出去的内存的起始位置。

##MemPool实现##

CMemPool::CMemPool(unsigned long ulUnitNum, unsigned long ulUnitSize):
m_pMemBlock(NULL), m_pAllocatedMemBlock(NULL), m_pFreeMemBlock(NULL),
m_ulBlockSize(ulUnitNum * (ulUnitSize+sizeof(struct _Unit))),
m_ulUnitSize(ulUnitSize)
{
	m_pMemBlock = malloc(m_ulBlockSize);
	if(NULL != m_pMemBlock)
	{
		for(unsigned long i = 0; i<ulUnitNum; i++)
		{
			struct _Unit* pCurUnit=(struct _Unit*)((char*)m_pMemBlock
			+ i*(ulUnitSize+sizeof(struct _Unit)) );
			pCurUnit->pPrev = NULL;
			pCurUnit->pNext = m_pFreeMemBlock;
			if(NULL != m_pFreeMemBlock)
			{
				m_pFreeMemBlock->pPrev = pCurUnit;
			}
			m_pFreeMemBlock = pCurUnit;
		}
	}
}

构造函数设置默认的小块数为50,每个小快大小为1024,最后用双向链表管理它们,m_pFreeMemBlock指向开始。

void* CMemPool::Alloc(unsigned long ulSize, bool bUseMemPool)
{
	if(ulSize > m_ulUnitSize || false == bUseMemPool ||
	NULL == m_pMemBlock || NULL == m_pFreeMemBlock)
	{
		cout << "System Call" << endl;
		return malloc(ulSize);		
	}
	struct _Unit *pCurUnit = m_pFreeMemBlock;
	m_pFreeMemBlock = pCurUnit->pNext;
	if(NULL != m_pFreeMemBlock)
	{
		m_pFreeMemBlock->pPrev = NULL;
	}
	pCurUnit->pNext = m_pAllocatedMemBlock;
	if(NULL != m_pAllocatedMemBlock)
	{
		m_pAllocatedMemBlock->pPrev = pCurUnit;
	}
	m_pAllocatedMemBlock = pCurUnit;
	cout << "Memory Pool" << endl;
	return (void*)((char*)pCurUnit + sizeof(struct _Unit));
}

Alloc的作用是分配内存,返回分配的内存地址,注意加上Unit的大小是为了略过Unit管理头。实质是把m_pFreeMemBlock指向的free内存移动到m_pAllocatedMemBlock指向的已分配内存里。

每次分配时,m_pFreeMemBlock指针后移。pCurUnit从前面插入到m_pAllocatedMemBlock里。

void CMemPool::Free(void* p)
{
	if(m_pMemBlock<p && p<(void*)((char*)m_pMemBlock + m_ulBlockSize))
	{
		//判断释放的内存是不是处于CMemPool
		cout << "Memory Pool Free" << endl;
		struct _Unit* pCurUnit = (struct _Unit*)((char*)p - 
		sizeof(struct _Unit));
		m_pAllocatedMemBlock = pCurUnit->pNext;
		if(NULL != m_pAllocatedMemBlock)
		{
			m_pAllocatedMemBlock->pPrev == NULL;
		}
		pCurUnit->pNext = m_pFreeMemBlock;
		if(NULL != m_pFreeMemBlock)
		{
			m_pFreeMemBlock->pPrev = pCurUnit;
		}
		m_pFreeMemBlock = pCurUnit;
	}
	else
	{
		free(p);
	}
}

Free的作用是释放内存,实质是把m_pAllocatedMemBlock指向的已分配内存移动到m_pFreeMemBlock指向的free内存里。和Alloc的作用相反。

pCurUnit要减去struct _Unit是为了从存储单元得到管理头的位置,堆是向上生长的。

##测试##

#include "mempool.h"
#include <time.h> 
CMemPool g_MemPool;
class CTestClass
{
public:
	void *operator new(size_t);	 //重载运算符new
	void operator delete(void *p);
	
private:
	char m_chBuf[1000];
};
void *CTestClass::operator new(size_t uiSize)
{
	return g_MemPool.Alloc(uiSize); //分配g_MemPool的内存给它
}
void CTestClass::operator delete(void *p)
{
	g_MemPool.Free(p);
}
class CTestClass2
{
private:
	char m_chBuf[1000];
};
timespec diff(timespec start, timespec end)
{
 timespec temp;
 temp.tv_sec = end.tv_sec-start.tv_sec;
 temp.tv_nsec = end.tv_nsec-start.tv_nsec;
 return temp;
}
int main()
{
 timespec time1, time2;
	for(int iTestCnt=1; iTestCnt<=10; iTestCnt++)
	{
		unsigned int i;
		//使用内存池测试
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1);
		for(i=0; i<100000*iTestCnt; i++)
		{
			CTestClass *p = new CTestClass;	
			delete p;
		}
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time2);
		
		cout << "[ Repeat " << 100000*iTestCnt << " Times ]" 
		<< "Memory Pool Interval = " << diff(time1,time2).tv_nsec 
		<< "ns" << endl;
		
		//使用系统调用测试
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1);
		for(i=0; i<LOOP_TIMES; i++)
		{
			CTestClass2 *p = new CTestClass2;	
			delete p;
		}
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time2);
		cout << "[ Repeat " << LOOP_TIMES << " Times ]" 
		<< "System Call Interval = " << diff(time1,time2).tv_nsec 
		<< "ns" << endl;
	}
	return 0;
}

##结果##

从下图可以看出,只有当程序频繁地用系统调用malloc/free或者new/delete分配内存时,内存池有价值。

C++内存池实现

 



Tags:C++ 内存池   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
内存池是自己向OS请求的一大块内存,自己进行管理。##系统调用## 我们先测试系统调用new/delete的用时。#include <iostream>#include <time.h> using namespace std;class Te...【详细内容】
2019-08-28  Tags: C++ 内存池  点击:(252)  评论:(0)  加入收藏
▌简易百科推荐
一、简介很多时候我们都需要用到一些验证的方法,有时候需要用正则表达式校验数据时,往往需要到网上找很久,结果找到的还不是很符合自己想要的。所以我把自己整理的校验帮助类分...【详细内容】
2021-12-27  中年农码工    Tags:C#   点击:(2)  评论:(0)  加入收藏
引言在学习C语言或者其他编程语言的时候,我们编写的一个程序代码,基本都是在屏幕上打印出 hello world ,开始步入编程世(深)界(坑)的。C 语言版本的 hello world 代码:#include <std...【详细内容】
2021-12-21  一起学嵌入式    Tags:C 语言   点击:(11)  评论:(0)  加入收藏
读取SQLite数据库,就是读取一个路径\\192.168.100.**\position\db.sqlite下的文件<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0"/...【详细内容】
2021-12-16  今朝我的奋斗    Tags:c#   点击:(21)  评论:(0)  加入收藏
什么是shellshell是c语言编写的程序,它在用户和操作系统之间架起了一座桥梁,用户可以通过这个桥梁访问操作系统内核服务。 它既是一种命令语言,同时也是一种程序设计语言,你可以...【详细内容】
2021-12-16  梦回故里归来    Tags:shell脚本   点击:(18)  评论:(0)  加入收藏
一、编程语言1.根据熟悉的语言,谈谈两种语言的区别?主要浅谈下C/C++和PHP语言的区别:1)PHP弱类型语言,一种脚本语言,对数据的类型不要求过多,较多的应用于Web应用开发,现在好多互...【详细内容】
2021-12-15  linux上的码农    Tags:c/c++   点击:(17)  评论:(0)  加入收藏
1.字符串数组+初始化char s1[]="array"; //字符数组char s2[6]="array"; //数组长度=字符串长度+1,因为字符串末尾会自动添&lsquo;\0&lsquo;printf("%s,%c\n",s1,s2[2]);...【详细内容】
2021-12-08  灯-灯灯    Tags:C语言   点击:(47)  评论:(0)  加入收藏
函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。在参数传递中,有两个很...【详细内容】
2021-11-30  小智雅汇    Tags:函数   点击:(19)  评论:(0)  加入收藏
一、问题提出问题:把m个苹果放入n个盘子中,允许有的盘子为空,共有多少种方法?注:5,1,1和1 5 1属同一种方法m,n均小于10二、算法分析设f(m,n) 为m个苹果,n个盘子的放法数目,则先对...【详细内容】
2021-11-17  C语言编程    Tags:C语言   点击:(49)  评论:(0)  加入收藏
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  深度Linux    Tags:C++   点击:(38)  评论:(0)  加入收藏
OpenCV(Open Source Computer Vision Library)是一个(开源免费)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android、ios等操作系统上,它轻量级而且高效---由一系列...【详细内容】
2021-11-11  zls315    Tags:C#   点击:(50)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条