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

相关推荐
Flying_Fish_roe1 小时前
JVM 性能优化与调优-Shenandoah GC
jvm·性能优化
ZachOn1y2 小时前
Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 死亡对象判断方法
java·jvm·后端·java-ee·团队开发·个人开发
ZachOn1y2 小时前
Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 内存分配和回收规则
java·jvm·后端·个人开发
ice___Cpu5 小时前
JVM && JMM 专题篇 ( 12000 字详解 )
jvm
小小小小关同学9 小时前
【JVM】垃圾回收
jvm
知初~9 小时前
JVM源码解析
jvm
小小小小关同学9 小时前
【JVM】运行时数据区
jvm
Evan Wang17 小时前
七、垃圾收集器ParNew&CMS与底层三色标记算法详解
java·jvm·算法
niceffking20 小时前
JVM 运行时数据区域
java·jvm
镭速1 天前
镭速如何高效使用SQLite数据库高速传输结构化数据文件传输
jvm·数据库·sqlite