当前位置: 首页 > news >正文

高并发内存池(定长内存池基础)

定长内存池的设计

      • 定长内存池
      • 定长内存池的原理讲解
      • 代码实现
            • 定义对象
            • New对象的主要逻辑
            • delete对象的主要逻辑
            • 完整代码

定长内存池

为什么我们要设计这个定长内存池呢?首先malloc是c标准库中向堆申请空间的接口,变相的说malloc是普遍性,而我们要实现的高并发内存池是特殊性,是针对某种情况下把效率提高到极致的一种申请空间的方法。在后续我们的项目中也会用到定长内存池,所以定长内存池的设计有两方面,一是让我们熟悉一下内存的分配问题,二是他会作为我们的高并发内存池的一个基础组件。

定长内存池的原理讲解

首先我们需要在内存中申请一块固定大小的空间,如下图:
在这里插入图片描述

后面我们考虑如何分配空间以及管理后续的空间。这里我们像系统申请了一大块空间,如果再有用户需要申请空间这个时候我们就可以直接从我们管理的那一大块空间中取出空间,从而避免了重复申请和释放空间的效率问题。但是这里用户归还的空间我们也不能直接还给操作系统,所以我们需要使用一个freelist(自由链表)将归还的空间管理起来

在这里插入图片描述
在这里插入图片描述
每次从上面取完空间后,归还空间我们就直接头插到链表中。下次如果在需要申请,我们就可以直接使用_freelist中的空间直接给到用户使用。

代码实现

定义对象

我们先定义一个class ObjectPool类对象

#pragma once
#include<iostream>
using std::endl;
using std::cout;

template<class T>
class Object {
public:
private:
	char* _memory = nullptr; //指向内存池的起始位置
	size_t _remain = 0;      //内存剩余空间大小
	void* _freelist = nullptr; //指向自由链表的第一个节点的指针
};

这里定义三个私有成员变量,具体用处见上述代码注释。

New对象的主要逻辑

首先我们对于空间申请的类,肯定就是new 和 delete两个重要的接口。我们先来写new:

T* New()
{
	T* obj = nullptr; //先顶一个返回的obj
	if (_freelist)    //如果自由链表不为空,此时我们直接使用自由链表的空间
	{
		//这里就相当于链表的头删
		void* next = *(void**)_freelist; //定义一个next存储链表下一个节点的地址 
		obj = (T*)_freelist; //再让obj指向分配内存的首地址
		_freelist = next;    //再将自由链表连接到下一个节点处
	}
	else{
		if (_remain<sizeof(T))  //如果内存空间剩余大小小于当前类型的大小
		{
			//重新像系统申请一块大空间
			_remain = 128 * 1024; //更新内存剩余空间大小
			_memory = (char*)malloc(_remain); //开辟空间
			if (_memory == nullptr)  //如果_memory为空则报错
			{
				throw std::bad_alloc();
			}
		}
	obj = (T*)_memory;  //obj接收被切割的首地址
	size_t objsize = sizeof(T) < sizeof(void*) ?  sizeof(void*) : sizeof(T);  //这里为了防止开辟的空间没有办法储存地址,所以如果类型大小小于地址的大小,则开辟地址大小字节的空间,否则开辟类型大小的空间。
	_memory += objsize; //随后_memory指向后面的空间
	_remain -= objsize; //_remain减少
	//显示调用一下构造,应对string等这些类型的构造
	new(obj)T;
	return obj;
	}
}
delete对象的主要逻辑

delete就相当于是链表的头插

void Delete(T* obj)
{
	obj->~T();
	*(void**)obj = _freelist;
	_freelist = obj;
}

这里的主要逻辑就结束了,接下来对代码进行优化一下。
我们这里使用的还是malloc,所以我们还可以完全脱离malloc使用系统调用接口virtualalloc这个接口直接在堆上申请空间,接下来就是完整代码,还有一段测试的例子我们可以清楚的看到我们的定长内存池在申请同一类型的空间时效率的提升有多夸张。

完整代码

这里我们使用了条件编译用来区分windows和Linux中不同的系统调用接口。

#pragma once
#include<iostream>
#include<vector>

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()
	{
		T* obj = nullptr;
		if (_freelist)//如果自由链表不为空则使用自由链表中的空间
		{
			void* next = *(void**)_freelist;
			obj = (T*)_freelist;
			_freelist = next;
		}
		else {
			if (_remain < sizeof(T))
			{
				_remain = 128 * 1024;
				//_memery = (char*)malloc(_remain);
				_memery = (char*)SystemAlloc(_remain>>13);
				if (_memery == nullptr)
				{
					throw std::bad_alloc();
				}
			}
			obj = (T*)_memery;
			size_t objsize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
			_memery += objsize;
			_remain -= objsize;
			
		}
		new(obj)T;
		
		return obj;
	}
	void Delete(T* obj)
	{
		obj->~T();
		*(void**)obj = _freelist;
		_freelist = obj;
	}

private:
	char* _memery = nullptr;//内存池起始地址
	size_t _remain = 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:
在这里插入图片描述

相关文章:

  • STM32 认识STM32
  • 【AI飞】AutoIT入门一:AutoIT来了,准备让AI动起来
  • 数据库实战篇,SQL在Kooboo中的实际应用(一)
  • Epplus 8+ 许可证设置
  • ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(系列开篇)
  • 【NLP】25.python实现点积注意力,加性注意力,Decoder(解码器)与 Attention
  • 六、adb通过Wifi连接
  • cut命令:剪切
  • LeetCode[18]四数之和
  • 江顺科技应收账款期后回款比率大降:现金流急剧减少,研发费用率下滑
  • Unity中计算闭合路径内部的所有点位
  • Kubenetes-基于kubespray 部署集群
  • 鸿蒙开发-编译器使用
  • 如何 在 Cesium 中选取特定经纬度区域,特定视角 ,渲染成图片
  • 什么叫“架构”
  • 交通运输部4项网络与数据安全标准发布
  • Bash脚本编写基础指南
  • 对接印度尼西亚股票数据源API
  • Linux ELF文件格式
  • 【笔记ing】AI大模型-03深度学习基础理论
  • 北美票房|《罪人》成首部观众评分为A级的恐怖片
  • 重大虚开发票偷税骗补案被查处:价税2.26亿,涉700余名主播
  • 影子调查丨义门陈遗址建筑被“没收”风波
  • 鲁比奥称“美或退出俄乌谈判”,欧洲官员:为了施压乌克兰
  • 张宝亮任山东临沂市委书记
  • 许志强评《伐木》|伯恩哈德的文人共和国