项目篇-----内存池(一)

温馨提示:本文章略显恋爱脑,仅用于举例说明!!!切勿模仿!!!

1.什么是内存池

我们知道,我们程序中的变量函数等等都是占用内存的,那么如果每次在使用的时候都去向CPU内存去申请就很麻烦,为此,我们可以造一个"池子",先提前在里面申请足够大的内存,这样当我们需要内存时,就去这个池子里面申请就好了,如果不明白可以看下面这个例子,

就好比一对小情侣在恋爱的时候难免要约会吃饭,吃喝玩乐,这些都是要有金钱花费的,一种方法就是两个人AA,另一种方法就是开一个恋爱基金账户,提起在里面存好足够的钱,之后有共同花销时就直接去这个恋爱基金库里面拿钱,这里的恋爱基金库就对应的计算机领域的内存池!!!

2.创建内存池

了解内存池后,我们开始创建一个定长的内存池!这里的定长指的是它每次只能申请固定大小的内存,不能按需索取!

要创建内存池,首先要定义指向大块内存的指针(恋爱基金的账户号),大块内存的剩余内存大小(恋爱基金的剩余金额),以及归还回来的内存的管理方式,用一个freelist管理。

下面由于代码注释很详细,这里直接给出定长内存池代码!

复制代码
template<class T>
class ObjectPool
{
public:
	//申请内存函数
	T* New()
	{
		//先定义obj对象
		T* obj = nullptr;
		//申请内存,先从freelist上面申请,不够了再去开大块空间
		if (_freelist)
		{
			//有还回来的空间,且空间足够所需,
			//"头删"一块内存给obj对象
			void* next = *(void**)_freelist;
			obj = (T*)_freelist;
			_freelist = next;
		}
		else
		{
			// 剩余内存不够一个对象大小时,则重新开大块空间
			if (_remainbytes < sizeof(T))
			{
				_remainbytes = 128*1024;//向大内存申请空间
				_memory = (char*)malloc(_remainbytes);
				if (_memory == nullptr)//检测申请成功与否
				{
					throw std::bad_alloc();
				}
			}
			//内存现在申请成功了,准备给Obj对象分配
			obj = (T*)_memory;
			size_t ObjSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
			_memory += ObjSize;
			_remainbytes -= ObjSize;
		}
		// 定位new,显示调用T的构造函数初始化
		new(obj)T;
		return obj;
	}
	void Delete(T* obj)
	{
		// 显示调用析构函数清理对象
		obj->~T();
		//归还的内存以头插的方式由freelist管理
		//头插
		*(void**)obj = _freelist;
		_freelist = obj;
	}
private:
	char* _memory = nullptr;// 指向大块内存的指针,使用char是方便后续进行内存分配
	void* _freelist = nullptr;// 还回来过程中链接的自由链表的头指针,即还回来的内存用freelist仿照链表的形式
								//来进行管理,
	size_t _remainbytes = 0;// 大块内存在切分过程中剩余字节数
};

3.效率比较

随后进行效率比较:new和内存池效率(恋爱基金库和AA哪种方式支付的效率更高)

我们假设都是申请treenode节点若干次,比较二者时间即可,下面给出代码:

复制代码
struct TreeNode
{
	int _val;
	TreeNode* _left;
	TreeNode* _right;

	TreeNode()
		:_val(0)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

void TestObjectPool()
{
	// 申请释放的轮次
	const size_t Rounds = 5;

	// 每轮申请释放多少次
	const size_t N = 100000;

	std::vector<TreeNode*> v1;
	v1.reserve(N);

	//比较效率:new和内存池谁的效率更高
	
	//new:
	size_t begin1 = clock();
	for (size_t j = 0; j < Rounds; ++j)
	{
		for (int i = 0; i < N; ++i)
		{
			v1.push_back(new TreeNode);
		}
		for (int i = 0; i < N; ++i)
		{
			delete v1[i];
		}
		v1.clear();
	}

	size_t end1 = clock();

	std::vector<TreeNode*> v2;
	v2.reserve(N);

	//内存池:
	ObjectPool<TreeNode> TNPool;
	size_t begin2 = clock();
	for (size_t j = 0; j < Rounds; ++j)
	{
		for (int i = 0; i < N; ++i)
		{
			v2.push_back(TNPool.New());
		}
		for (int i = 0; i < N; ++i)
		{
			TNPool.Delete(v2[i]);
		}
		v2.clear();
	}
	size_t end2 = clock();

	cout << "new cost time:" << end1 - begin1 << endl;
	cout << "object pool cost time:" << end2 - begin2 << endl;
}

二者时间如下:

我们发现有了内存池,效率大大提高了,就好比跟女朋友约会的花销,要是有恋爱基金的话,支付很快,刷一下卡就Ok,但是要是没有的话就要拿出计算器算两个人AA慢慢算了~会耽误约会时间~

最后代码汇总一下:

复制代码
#pragma once
#include<iostream>
#include<vector>
#include<time.h>

//不要全部展开,会造成污染
using std::cout;
using std::endl;

#ifdef _WIN32
#include<windows.h>
#else
// 
#endif

// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	// linux下brk mmap等
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();

	return ptr;
}

template<class T>
class ObjectPool
{
public:
	//申请内存函数
	T* New()
	{
		//先定义obj对象
		T* obj = nullptr;
		//申请内存,先从freelist上面申请,不够了再去开大块空间
		if (_freelist)
		{
			//有还回来的空间,且空间足够所需,
			//"头删"一块内存给obj对象
			void* next = *(void**)_freelist;
			obj = (T*)_freelist;
			_freelist = next;
		}
		else
		{
			// 剩余内存不够一个对象大小时,则重新开大块空间
			if (_remainbytes < sizeof(T))
			{
				_remainbytes = 128*1024;//向大内存申请空间
				_memory = (char*)malloc(_remainbytes);
				if (_memory == nullptr)//检测申请成功与否
				{
					throw std::bad_alloc();
				}
			}
			//内存现在申请成功了,准备给Obj对象分配
			obj = (T*)_memory;
			size_t ObjSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
			_memory += ObjSize;
			_remainbytes -= ObjSize;
		}
		// 定位new,显示调用T的构造函数初始化
		new(obj)T;
		return obj;
	}
	void Delete(T* obj)
	{
		// 显示调用析构函数清理对象
		obj->~T();
		//归还的内存以头插的方式由freelist管理
		//头插
		*(void**)obj = _freelist;
		_freelist = obj;
	}
private:
	char* _memory = nullptr;// 指向大块内存的指针,使用char是方便后续进行内存分配
	void* _freelist = nullptr;// 还回来过程中链接的自由链表的头指针,即还回来的内存用freelist仿照链表的形式
								//来进行管理,
	size_t _remainbytes = 0;// 大块内存在切分过程中剩余字节数
};

struct TreeNode
{
	int _val;
	TreeNode* _left;
	TreeNode* _right;

	TreeNode()
		:_val(0)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

void TestObjectPool()
{
	// 申请释放的轮次
	const size_t Rounds = 5;

	// 每轮申请释放多少次
	const size_t N = 100000;

	std::vector<TreeNode*> v1;
	v1.reserve(N);

	//比较效率:new和内存池谁的效率更高
	
	//new:
	size_t begin1 = clock();
	for (size_t j = 0; j < Rounds; ++j)
	{
		for (int i = 0; i < N; ++i)
		{
			v1.push_back(new TreeNode);
		}
		for (int i = 0; i < N; ++i)
		{
			delete v1[i];
		}
		v1.clear();
	}

	size_t end1 = clock();

	std::vector<TreeNode*> v2;
	v2.reserve(N);

	//内存池:
	ObjectPool<TreeNode> TNPool;
	size_t begin2 = clock();
	for (size_t j = 0; j < Rounds; ++j)
	{
		for (int i = 0; i < N; ++i)
		{
			v2.push_back(TNPool.New());
		}
		for (int i = 0; i < N; ++i)
		{
			TNPool.Delete(v2[i]);
		}
		v2.clear();
	}
	size_t end2 = clock();

	cout << "new cost time:" << end1 - begin1 << endl;
	cout << "object pool cost time:" << end2 - begin2 << endl;
}

好了本篇文章到此结束,下一篇将正式进入内存池的项目练习,本篇属于开胃小菜

希望大家不要向博主一样恋爱脑,写博客还不忘了想女朋友,专心学习,相信能看到这里的应该都不缺对象了,因为可以随时随地的new一个出来嘛~

(开玩笑的,请勿当真,代码要好好写,恋爱也要好好谈哦~)

相关推荐
zhuqiyua5 小时前
第一次课程家庭作业
c++
只是懒得想了5 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法
m0_736919105 小时前
模板编译期图算法
开发语言·c++·算法
玖釉-5 小时前
深入浅出:渲染管线中的抗锯齿技术全景解析
c++·windows·图形渲染
【心态好不摆烂】5 小时前
C++入门基础:从 “这是啥?” 到 “好像有点懂了”
开发语言·c++
dyyx1115 小时前
基于C++的操作系统开发
开发语言·c++·算法
AutumnorLiuu6 小时前
C++并发编程学习(一)——线程基础
开发语言·c++·学习
m0_736919106 小时前
C++安全编程指南
开发语言·c++·算法
阿猿收手吧!6 小时前
C++ std::lock与std::scoped_lock深度解析:从死锁解决到安全实践
开发语言·c++
蜡笔小马6 小时前
11.空间索引的艺术:Boost.Geometry R树实战解析
算法·r-tree