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

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

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一个出来嘛~

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

相关推荐
shehuiyuelaiyuehao36 分钟前
算法29,前缀和,除自身以外的数组的乘积
java·数据结构·算法
智购科技自动售货机厂家40 分钟前
2026自动售货机设备清洁效果自动验证:从图像比对到评分算法的工程实践~YH
人工智能·算法·计算机视觉
爱吃巧克力的程序媛1 小时前
main.cpp注册 C++ 类到 QML 元对象系统
c++·qt
YYYing.1 小时前
【C++进阶系列 (二)】关于线程堆栈的那些事——函数调用过程
开发语言·c++·函数·线程堆栈
姜穆澜1 小时前
机器学习实战指南:从算法原理到工程落地
人工智能·算法·机器学习
Q一件事2 小时前
RWEQ——保留与消去P的soil_loss联合推导
算法
键盘会跳舞2 小时前
【C++多线程】死锁诊断工具实战:从 Windows 到 Linux 的全链路排查
linux·c++·windows·死锁
HugoStudio_SWAN3 小时前
洛谷 P10719 \[GESP202406 五级] 黑白格——暴力美学与图像处理的最小外接矩形
c++·图像处理·人工智能·学习·程序人生·算法·目标跟踪
那年窗外下的雪.3 小时前
AIDC 学习日志|第 20 天|多归属业务验收与哈希不均定位
学习·算法·哈希算法