【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模式:

相关推荐
yuanbenshidiaos10 小时前
c++---------数据类型
java·jvm·c++
java1234_小锋13 小时前
JVM对象分配内存如何保证线程安全?
jvm
40岁的系统架构师16 小时前
1 JVM JDK JRE之间的区别以及使用字节码的好处
java·jvm·python
寻找沙漠的人16 小时前
理解JVM
java·jvm·java-ee
我叫啥都行16 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
bufanjun00119 小时前
JUC并发工具---ThreadLocal
java·jvm·面试·并发·并发基础
东阳马生架构1 天前
JVM简介—1.Java内存区域
jvm
工程师老罗1 天前
Android笔试面试题AI答之SQLite(2)
android·jvm·sqlite
Qzer_4071 天前
jvm字节码中方法的结构
jvm
奇偶变不变2 天前
RTOS之事件集
java·linux·jvm·单片机·算法