【Buffer Pool】定长内存池的实现

创建一个大块的内存内存

1.内存的类型是什么? char* 方便有多少字节就乘以多少字节

2.如何还回来内存?可以将换回来的小块的内存块链接起来,使用freeList

3.如何链接起来? 让上一个内存块的数据存下一个内存块的地址即可

4.如果内存块的定长大小为20字节,该如何存储地址呢? 如果是32位下,指针大小为4字节,就划分内存的前4字节的空间存储地址。如果是64位下,就划分前8字节的空间存储地址。

5.如果内存块的定长大小不足4字节获得8字节呢?

6.如果没有剩余空间了,如何判断? 使用成员变量remainBytes,表示大块内存剩余的空间

7.第一次还回来了一个对象(内存),该怎么处理?让内存块头4字节或者8字节(一个指针的大小)指向空

7.1如何判断环境是32位还是64位? 对类型指针就行了,void是类型,void*是void的指针

*(void**)等于void*(对void*指针再解引用,为什么要这么写?因为这样写可以对该对象进行写入了),void*是指针类型,大小就是指针的大小

8.刚刚解决了第一次还回来的问题,那第二次第三次呢? 可以头插

并且,第一次和第二次是相同的,头插都可以解决

9.有没有一种可能性,大内存块切了一部分,同时又有一部分内存还回来了,这时候接下来如果要继续申请内存,该怎么处理呢?是用原来的大内存块继续切还是用还回来的内存呢?

全部代码:

cpp 复制代码
#pragma once
#include<iostream>
#include<vector>
#include<time.h>
using std::cout;
using std::endl;

template<class T>
class ObjectPool
{
public:
	//申请内存
	T* New()
	{
		T* obj = nullptr;
		//1.优先把换回来的内存块再次重复利用
		if (_freeList)  //代表有还回来的内存块对象
		{
			//重复利用,进行单链表的头删 
			//这里的*(void**)_freeList表示取freeLiset的前指针大小个字节 -->也就是取next的地址
			void* next = *((void**)_freeList);     //?  这里为什么使用void**

			//obj是取整个对象
			obj = (T*)_freeList;
			//往后移动一位
			_freeList = next;
		}
		else  //如果没有还回来的内存块
		{
			//判断开辟的一大块空间是否够这次申请的
			if (_remainBytes < sizeof(T))  
			{
				//如果不够
				_remainBytes = 1024 * 128;   //申请128KB
				//这里为什么要这么写?
				_memory = (char*)malloc(_remainBytes);
				//SystemAlloc是系统调用接口
				//_memory = (char*)SystemAlloc(_remainBytes >> 13);
				
				//异常处理
				if (_memory == nullptr)
				{
					throw std::bad_alloc();
				}
			}
			//创建一个对象指向memory,这个obj也就是我们申请内存给到的实体
			obj = (T*)_memory;

			//这里判断的意义是:如果我们申请的空间大于指针的大小,就返回我们申请的空间,
			//否则返回指针的大小  --> 这样做是为了内存块至少能存下地址
			size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);

			//大块内存的指针向后移动
			_memory += objSize;
			//可用空间减少
			_remainBytes -= objSize;
		}

		new(obj) T;

		return obj;
	}

	//这是释放的过程,将释放的小内存块挂到_freeList前面
	void Delete(T* obj)
	{
		//显式调用函数清理对象
		obj->~T();

		//头插
		//将obj内存块的前指针大小的字节存入_freeList的地址
		*(void**)obj = _freeList;
		//将_freeList移动为头结点
		_freeList = obj;
	}
private:
	char* _memory = nullptr;         //指向大块内存的指针
	size_t _remainBytes = 0;         //大块内存切分过程中剩余字节数
	void* _freeList = nullptr;      //还回来过程中链接的滋有链表的头指针
};


//测试
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);

	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;
}

运行结果:debug模式

release模式:

相关推荐
m0_748554816 小时前
golang如何实现用户订阅偏好管理_golang用户订阅偏好管理实现总结
jvm·数据库·python
lee_curry6 小时前
第四章 jvm中的垃圾回收器
java·jvm·垃圾收集器
阿正呀7 小时前
Redis怎样实现本地缓存的高效失效通知
jvm·数据库·python
2501_901200538 小时前
mysql如何设置InnoDB引擎参数_优化innodb_buffer_pool
jvm·数据库·python
金銀銅鐵8 小时前
[java] 编译之后的记录类(Record Classes)长什么样子(上)
java·jvm·后端
m0_495496419 小时前
mysql处理复杂SQL性能_InnoDB优化器与MyISAM差异
jvm·数据库·python
forEverPlume9 小时前
PHP怎么使用Eloquent Attribute Composition属性组合_Laravel通过组合构建复杂属性【方法】
jvm·数据库·python
2301_8092047010 小时前
mysql在docker容器中如何部署_利用docker-compose快速启动
jvm·数据库·python
2301_8159019714 小时前
SQL如何将多行记录聚合成逗号分隔字符串_GROUP_CONCAT技巧
jvm·数据库·python
zjy2777714 小时前
Layui tab选项卡如何动态根据ID值进行程序化切换
jvm·数据库·python