内存池技术

news2024/11/24 0:23:00

为了学习池化技术以及后续自行实现一个仿tcmalloc的线程池,我们先浅浅的学习一下池化的概念,以及简单的实现一个定长的内存池。

文章目录

  • 一:池化技术
  • 二:内存池
  • 三:内存池主要解决的问题
  • 四:malloc
  • 五:牛刀小试-定长内存池

一:池化技术

所谓“池化技术”,就是程序先向系统申请过量的资源,然后自己管理,以备不时之需。之所以要申请过量的资源,是因为每次申请该资源都有较大的开销,不如提前申请好了,这样使用时就会变得非常快捷,大大提高程序运行效率。在计算机中,有很多使用“池”这种技术的地方,除了内存池,还有连接池、线程池、对象池等。
以服务器上的线程池为例,它的主要思想是:先启动若干数量的线程,让它们处于睡眠状态,当接收到客户端的请求时,唤醒池中某个睡眠的线程,让它来处理客户端的请求,当处理完这个请求,线程又进入睡眠状态。

二:内存池

内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。

三:内存池主要解决的问题

  1. 效率问题
  2. 内存碎片问题
    在这里插入图片描述
    再需要补充说明的是内存碎片分为外碎片内碎片,上面这张图描述的是外碎片问题。
    💡外部碎片是一些空闲的连续内存区域太小,这些内存空间不连续,以至于合计的内存足够,但是不能满足一些的内存分配申请需求。
    💡内部碎片是由于一些对齐的需求,导致分配出去的空间中一些内存无法被利用。

四:malloc

C/C++中我们要动态申请内存都是通过malloc去申请内存,但是我们要知道,实际我们不是直接去堆获取内存的,而malloc就是一个内存池。malloc() 相当于向操作系统“批发”了一块较大的内存空间,然后“零售”给程序用。当全部“售完”或程序有大量的内存需求时,再根据实际需求向操作系统“进货”。malloc的实现方式有很多种,一般不同编译器平台用的都是不同的。

五:牛刀小试-定长内存池

⭐️特点:

  1. 性能达到极致
  2. 不考虑内存碎片问题

作为程序员(C/C++)我们知道申请内存使用的是malloc,malloc其实就是一个通用的大众货,什么场景下都可以用,但是什么场景下都可以用就意味着什么场景下都不会有很高的性能,下面我们就先来设计一个定长内存池做个开胃菜。

#include<iostream>
#include<vector>
#include<time.h>
using std::cout;
using std::endl;

//条件编译,兼顾跨平台
#ifdef _WIN32
	#include<Windows.h>
#else
	// linux
#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对象大小
		T* obj = nullptr;
		//优先把还回来的内存块对象再次重复利用
		if (_freeList)
		{
			//自由链表头删
			void* next = *((void**)_freeList);
			obj = (T*)_freeList;
			_freeList = next;
		}
		else
		{
			// 当剩余的内存不够一个对象的大小时,就重新开大块空间,剩余的就不要了,内碎片
			if (_remainBytes < sizeof(T))
			{
				_remainBytes = 128 * 1024;
				_memory = (char*)SystemAlloc(_remainBytes >> 13);//直接从堆区获得内存
				if (_memory == nullptr)
				{
					throw std::bad_alloc();
				}
			}
			//memory里面来切一块
			obj = (T*)_memory;
			size_t objSize = sizeof(T) < sizeof(T*) ? sizeof(void*) : sizeof(T);//按情况给多大的大小
			_memory += objSize;
			_remainBytes -= objSize;
		}
		//定位new,对已存在的空间显式调用T的构造函数初始化
		new(obj)T;
		return obj;
	}

	void Delete(T* obj)
	{	
		//显式调用析构函数清理对象
		obj->~T();
		//头插
		*(void**)obj = _freeList;//int**都可以,给个二级指针就可以
		_freeList = obj;
	}
private:
	size_t _remainBytes = 0;   //大块内存在被切分过程中剩余字节数
	char* _memory = nullptr;   //指向大块内存的指针
	void* _freeList = nullptr; //还回过程中链接的自由链表指针
};

struct TreeNode
{
	int _val;
	TreeNode* _left;
	TreeNode* _right;
	TreeNode()
		:_val(0)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

//测试代码
void TestObjectPool()
{
	// 申请释放的轮次
	const size_t Rounds = 3;
	// 每轮申请释放多少次
	const size_t N = 10000;
	size_t begin1 = clock();
	std::vector<TreeNode*> v1;
	v1.reserve(N);
	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();


	ObjectPool<TreeNode> TNPool;
	size_t begin2 = clock();
	std::vector<TreeNode*> v2;
	v2.reserve(N);
	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 cost time:28
object pool cost time:14
请按任意键继续. . .

可以看出来,针对定长的空间申请,我们设计的定长内存池可以有明显的效率提升!

备注:如果大家对代码的细节有疑惑的地方,欢迎大家评论区提出,我们一起探讨!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/609020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

原地顺时针旋转矩阵(leetcode 48.选择图像)

本题目在leetcode上有原题48. 旋转图像 详细讲解 顺时针旋转90&#xff0c;横变竖&#xff0c;竖变横。按圈分解&#xff0c;一圈圈的单独转&#xff0c;由外圈到内圈&#xff0c;不断分解。 每一圈转到位了&#xff0c;整个矩阵就旋转好了。 那么&#xff0c;问题来了&…

Photoshop史上最强更新,动动手指就能让AI替你修图

Photoshop 在最新的 Beta 版本中&#xff0c;融入了 Firely 智能 AI 创意填充功能&#xff0c;只要对图片进行简单地框选&#xff0c;就能实现生成对象、生成背景、扩展图像、移除对象以及更多创意功能&#xff0c;支持用自然语言输入指令&#xff0c;让 AI 替你完成创意填充。…

Jmeter常用的两大性能测试场景你都知道吗?

目录 一、阶梯式场景 二、波浪式场景 一、阶梯式场景 该场景主要应用在负载测试里面&#xff0c;通过设定一定的并发线程数&#xff0c;给定加压规则&#xff0c;遵循“缓起步&#xff0c;快结束”的原则&#xff0c;不断地增加并发用户来找到系统的性能瓶颈&#xff0c;进而有…

SpringCloud:分布式缓存之Redis分片集群

1.搭建分片集群 主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决&#xff1a; 海量数据存储问题 高并发写的问题 使用分片集群可以解决上述问题&#xff0c;如图: 分片集群特征&#xff1a; 集群中有多个master&#xff0c;每个master保存不同数据 …

管道通信详解

目录 一、进程通信原理 二、什么是管道 三、创建一个匿名管道 四、fork共享管道的原理 五、管道的特点 六、4中场景 七、命名管道 八、命名管道通信的原理 九、创建一个命名管道 十、上实例 一、进程通信原理 我们知道进程间相互独立&#xff0c;具有独立性。那么我们…

编译原理 SLR(1) 语法分析器的构建

编译原理 SLR(1) 语法分析器的构建 在我的博客查看&#xff1a;https://chenhaotian.top/study/compilation-principle-slr1/ 实验三 自底向上语法分析器的构建 项目代码&#xff1a;https://github.com/chen2438/zstu-study/tree/main/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%8…

冈萨雷斯DIP第10章知识点

文章目录 10.2 点、线和边缘检测10.2.2 孤立点的检测10.2.3 线检测10.2.4 边缘模型 10.3 阈值处理10.3.4 使用图像平滑改进全局阈值处理10.3.5 使用边缘改进全局阈值处理10.4 使用区域生长、区域分离与聚合进行分割 分割依据的灰度值基本性质是&#xff1a;不连续性和相似性。本…

计算机网络第二章——物理层(下)

提示&#xff1a;君子可内敛不可懦弱&#xff0c;面不公可起而论之 文章目录 2.1.7 数据交换方式为什么要进行数据交换数据交换的方式电路交换电路交换的优缺点报文交换报文交换的优缺分组交换分组交换的优缺点数据交换方式的选择数据报方式虚电路方式虚电路方式的特点数据报VS…

HJ29 字符串加解密

描述 对输入的字符串进行加解密&#xff0c;并输出。 加密方法为&#xff1a; 当内容是英文字母时则用该英文字母的后一个字母替换&#xff0c;同时字母变换大小写,如字母a时则替换为B&#xff1b;字母Z时则替换为a&#xff1b; 当内容是数字时则把该数字加1&#xff0c…

深入理解设计原则之依赖反转原则(DIP)【软件架构设计】

系列文章目录 C高性能优化编程系列 深入理解软件架构设计系列 深入理解设计模式系列 高级C并发线程编程 DIP&#xff1a;依赖反转原则 系列文章目录1、依赖反转原则的定义和解读2、稳定的抽象层3、依赖倒置原则和控制反转、依赖注入的联系小结 1、依赖反转原则的定义和解读 …

多线程事务回滚方法

多线程事务回滚方法 介绍案例演示线程池配置异常类实体类控制层业务层mapper工具类验证 解决方案使用sqlSession控制手动提交事务SqlSessionTemplate注入容器中改造业务层验证成功操作示例业务层改造 介绍 1.最近有一个大数据量插入的操作入库的业务场景&#xff0c;需要先做一…

Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching 论文精读

Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching 论文链接&#xff1a;[2305.13310] Matcher: Segment Anything with One Shot Using All-Purpose Feature Matching (arxiv.org) 代码链接&#xff1a;aim-uofa/Matcher: Matcher: Segment Anyt…

STM32 HAL库开发——基础篇

目录 一、基础知识 1.1 Cortex--M系列介绍 1.2 什么是stm32 1.3 数据手册查看 1.4 最小系统和 IO 分配 1.4.1 电源电路 1.4.2 复位电路 1.4.3 BOOT 启动电路 1.4.4 晶振电路 1.4.5 下载调试电路 1.4.6 串口一键下载电路 1.4.7 IO 分配 1.4.8 总结 1.5 开发工…

Spring:Spring框架中的核心类 ③

一、解读思想 1、用轮廓解读体系。 2、关注细节&#xff0c;不执着细节。 二、核心类设计 1、 容器接口和实现类 ApplicationContext 接口&#xff08;容器&#xff09; ①.读取配置文件 ②.注解形成bean 哪种形式的bean统一核心管理使用中心类。 2、 ApplicationCont…

MySQL 子查询

文章目录 子查询单行子查询多行子查询相关子查询 exists 子查询 所谓子查询就是 select 查询语句中还有 select 查询语句&#xff0c;里面的称为子查询或内查询&#xff0c;外面的称为主查询或外查询。 根据查询结果记录数量&#xff0c;子查询可以分为两类&#xff1a; 单行…

机器学习 | 分类问题

目录 一、K近邻算法 二、决策树 1.一些原理介绍 2.决策树案例与实践 三、距离 一、K近邻算法 我们引入accuracy_score&#xff0c;利用score()的方法评估准确性。k近邻算法中的k是一个超参数&#xff0c;需要事先进行定义。 k值得选取经验做法是一般低于训练样本得平方根…

排书 dfs 迭代加深 IDA* 剪枝 java

&#x1f351; 算法题解专栏 &#x1f351; 排书 给定 n n n 本书&#xff0c;编号为 1 ∼ n 1 \sim n 1∼n。 在初始状态下&#xff0c;书是任意排列的。 在每一次操作中&#xff0c;可以抽取其中连续的一段&#xff0c;再把这段插入到其他某个位置。 我们的目标状态是把…

【云原生-K8s】k8s可视化管理界面安装配置及比较【Kuboard篇】

总览 安装了k8s控制面板&#xff0c;方便日常的问题处理&#xff0c;查看资源状态信息&#xff0c;也可以增加子账号进行开放给其他人员使用&#xff0c;减少命令操作&#xff0c;提升工作效率 前置条件 须有一个正常使用的k8s集群附k8s v1.23版本搭建&#xff1a;https://…

amis框架实现sdk中使用tsx

1.开发过程中&#xff0c;由于自己和同事用的不同方式使用&#xff0c;本人使用react搭建的amis框架&#xff0c;同事用sdk使用方式搭建 2.开发过程中遇到问题&#xff0c;如果需求中出现amis无法满足的组件&#xff0c;需要自己进行自定义组件&#xff0c;而不同使用方式的am…

JVM内存变化分析实战

最近在一次项目压力测试时&#xff0c;监测到JVM内存明显的变化&#xff0c;由于之前开发工作中没有涉及到JVM相关的问题分析&#xff0c;所以特此借这个机会学习和记录。项目使用的JDK版本为 OpenJdk 1.8&#xff0c;虚拟机为 HotSpot。 1. 内存变化情况 在压力测试进行2H48…