【C++项目】手动实现一个定长内存池(了解内存分配机制、定长内存提高效率 附源码)

news2024/11/17 23:58:57

0000

定长内存池

  • 1.项目介绍
  • 2.代码部分
  • 3.测试结果
  • 4.相关细节分析总结

1.项目介绍

在这里插入图片描述
这是一个 C++ 中的对象池(Object Pool)的简单实现,用于更有效地管理对象的内存分配和回收。对象池是一种内存管理技术,旨在减少频繁分配和释放对象的开销,从而提高程序的性能。

  • 以下是该对象池的一些关键部分和功能:

New() 函数:用于获取一个新的对象。它首先检查是否有已归还的内存块,如果有,则使用归还的内存,否则从内存池中分配一个新的对象。它使用 malloc 来分配内存,然后使用定位 new 来调用对象的构造函数,初始化对象。

Delete() 函数:用于回收一个对象。它首先调用对象的析构函数以清理对象,然后将对象添加到内存池的归还链表中。为了解决不同对象类型的大小差异问题,它使用了一个二级指针 _freeList,这样不同类型的对象都可以通过 _freeList 连接在一起。

内存分配:初始时,对象池会通过 malloc 分配一块大内存块,大小为1024 * 128字节。然后,通过不断更新 _memory 指针和 _remainBytes 记录内存块的剩余空间,来实现对象的内存分配。

性能测试:在代码中,有一个测试函数 TestObjectPool,用于测试直接使用 new 和对象池分配的性能差异。它执行多轮的分配和释放操作,以测量两种方法的性能。

这个对象池的实现是为了演示目的而创建的,可以在需要管理大量相同类型对象的情况下提高性能

2.代码部分

  • ObjectPool.h
#pragma once
#include<vector>
#include<iostream>
#include<time.h>
template <class T>
class ObjectPool
{
public:
	T* New()
	{
		T* obj = nullptr; //给一个对象指针 指向内存
		if (_freeList) //如果有归还的内存先用归还内存
		{
			//next指向freelist+sizeof(T)的一个位置
			void* next = static_cast<void*>(static_cast<char*>(_freeList) + sizeof(T));
			//void* next = *((void**)_freeList);
			obj = (T*)_freeList;
			_freeList = next;
			return obj;
		}
		else
		{
			if (remainBytes < sizeof(T))//如果剩余的内存不够 就新开一块空间
			{

				_memory = (char*)malloc(1024 * 128); //初次加载内存池创建内存
				if (_memory == nullptr)
				{
					throw std::bad_alloc();//创建失败 抛出异常
				}
			}

			obj = (T*)_memory;
			size_t objsize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);//处理后期内存回收时异常
			_memory += objsize;//往后移
			remainBytes -= objsize;//更新remainBytes

			//定位new 用于调用T的构造函数初始话
			new(obj)	T;
			return obj;
		}

	}

	void Delete(T* obj)
	{
		//显示调用T的析构函数清理对象
		obj->~T();

		//这里有一个问题 :当T是char类型的时候 大小只有1字节
			//在32位系统下 指针是4字节 64位下:8字节 
			//_freeList = obj;    就会出现异常 所以要用一个二级指针解决这个问题
		*(void**)obj = _freeList;
		/*当使用二级指针(例如 int** 或 void**)时,它们只是指向另一个指针的指针。
		这些指针的大小通常不取决于所指向的数据类型,而是固定的,
		因此可以在不同位数的系统上正常工作*/
		_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 = 3;

	//每轮申请释放的次数
	const size_t N = 1000;


	//直接new  测试
	size_t begin1 = clock();
	std::vector<TreeNode*>v1;
	v1.reserve(N);//预留N个空间
	for (size_t j = 0; j < Rounds; j++)
	{
		//插入N个元素
		for (size_t i = 0; i < N; ++i)
		{
			v1.push_back(new TreeNode);//直接new
		}
		//删除N个元素
		for (size_t k = 0; k < N; ++k)
		{
			delete v1[k];
		}
		v1.clear();//释放空间
	}

	size_t end1 = clock();
	//用ObjectPool测试
	ObjectPool<TreeNode>TNPool;
	size_t begin2 = clock();
	std::vector<TreeNode*>v2;
	v2.reserve(N);
	for (size_t j = 0; j < Rounds; j++)
	{
		//插入N个元素
		for (size_t i = 0; i < N; ++i)
		{
			v2.push_back(TNPool.New());//用objectPool new
		}
		//删除N个元素
		for (size_t k = 0; k < N; ++k)
		{
			TNPool.Delete(v2[k]);
		}
		v2.clear();//释放空间
	}
	size_t end2 = clock();

	std::cout << "new const time:" << end1 - begin1 << std::endl;
	std::cout << "objectPool const time:" << end2 - begin2 << std::endl;
}
  • Test.c
#define _CRT_SECURE_NO_WARNINGS
#include"ObjectPool.h"
int main()
{
	TestObjectPool();
	return 0;
}

3.测试结果

0001
这里在处理大规模数据的时候效率差距会更明显一些,在这里测试中设置的规模Rounds和N的值设置得比较小(本人设备内存的原因),可以在自己的机器上设置,数据越大,效率差距越明显。

0002
0003

4.相关细节分析总结

  • 注意点1

0005
在new()函数这里,这里所说的异常指的是回收内存链表的维护,因为这个链表freelist要存一个指针,
0009
如果这个数据类型是char类型的话大小只有1字节,而指针在32位系统下是4字节,32位系统下是8字节,所以会出现异常。这样处理的话就能巧妙避免这个问题。0010

  • 注意点2
    0006
    定位new 在处理自定义数据类型的时候很有必要

定位 new:通过使用定位 new,可以在已分配的内存块上调用对象的构造函数,从而正确地初始化对象。这对于确保对象状态的一致性非常重要,尤其是在对象具有复杂的构造逻辑时。

显式调用析构函数:在 Delete 函数中,通过显式调用对象的析构函数,您可以确保对象在释放之前正确地清理其资源。这对于防止资源泄漏和确保对象的正确析构非常重要。

这两个操作一起确保对象池中的对象正确地管理其资源和生命周期。而不是仅仅释放内存,还确保对象的构造和析构逻辑得到执行。

  • 注意点3
    0007
    解决不同系统下数据类型不匹配问题

最后创作不易,点赞支持爱你~请添加图片描述

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

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

相关文章

大日志(大文件)查看工具

一款很不错的日志查看工具&#xff0c; 优势是能查看很大的日志文档。 无需安装&#xff0c;解压后运行即可&#xff1b; 有注册版&#xff0c;不注册也可以使用。 官方地址&#xff1a; LogViewer - Home page 一个下载地址&#xff1a; 日志查看工具UVviewsoft LogViewer(超大…

linux-动态库和静态库制作和使用

【静态连接和动态连接】C/C编程中的两种有效链接策略_c 动态链接 静态链接_SecureCode的博客-CSDN博客 静、动态库概念和各自优点 静&#xff1a; 动&#xff1a; 动态库&#xff1a;只有一份&#xff0c;运行时具体代码行才加载使用&#xff08;相对慢&#xff09;&#xff1…

FastAPI学习-27 使用@app.api_route() 设置多种请求方式

对同一个访问函数设置多个http 请求方式 api_route 使用 使用methods 参数设置请求方式 from fastapi import FastAPIapp FastAPI() app.api_route(/demo/b, methods[get, post]) async def demo2(): return {"msg": "demo2 success"}判断请求方式…

FreeRTOS学习笔记——四、任务的定义与任务切换的实现

FreeRTOS学习笔记——四、任务的定义与任务切换的实现 0 前言1 什么是任务2 创建任务2.1 定义任务栈2.2 定义任务函数2.3 定义任务控制块2.4 实现任务创建函数2.4.1 任务创建函数 —— xTaskCreateStatic()函数2.4.2 创建新任务——prvInitialiseNewTask()函数2.4.3 初始化任务…

08. 机器学习- 线性回归

文章目录 线性回归 LINEAR REGRESSION 从本次课程开始&#xff0c;大部分时候我将不再将打印结果贴出来了&#xff0c;因为太占用篇幅。小伙伴可以根据我的输出执行敲一遍代码来进行学习和验证。 同样是为了节省篇幅&#xff0c;我也不会再一行行那么仔细的解释代码了&#xff…

学信息系统项目管理师第4版系列24_整合管理

1. PMBOK 1.1. 自1987年以来&#xff0c;PMBOK-直是基于过程的项目管理标准的重要代表 1.1.1. 基于过程的方法是项目管理的基石 1.2. 从2021年开始&#xff0c;第7版PMBOK采用了基于原则的标准&#xff0c;其中包含了 12个项目管理基本原则&#xff0c;这些基本原则为有效的…

android studio 我遇到的Task :app:compileDebugJavaWithJavac FAILED问题及解决过程

前几天一个网友在学习我的一个小项目的时候&#xff0c;发现无法达到目的&#xff0c;在帮他解决问题的过程中发现他用的是最近的giraffe版本的as&#xff0c;我用的是老版本&#xff0c;没办法打开他的项目&#xff0c;没办法只能卸载我的as&#xff0c;安装了最近版的diraffe…

【计算机网络笔记】计算机网络的结构

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 文章目录 系列文章目录网络边缘接入网络数字用户线路 (DSL)电缆网络典型家庭网络的接入机构&#xff08;企业&#xff09;接入网络 (Ethernet)无线接入网络 网络核心Internet结构最后 计算机网络的结构…

排序算法-快速排序法(QuickSort)

排序算法-快速排序法&#xff08;QuickSort&#xff09; 1、说明 快速排序法是由C.A.R.Hoare提出来的。快速排序法又称分割交换排序法&#xff0c;是目前公认的最佳排序法&#xff0c;也是使用分而治之&#xff08;Divide and Conquer&#xff09;的方式&#xff0c;会先在数…

整理mongodb文档:副本集成员可以为偶数

个人博客 整理mongodb文档:副本集成员可以为偶数 想了下&#xff0c;仲裁节点还是不想直接说太多&#xff0c;怕有的同学想太多&#xff0c;且本身副本集就偏向运维的&#xff0c;新手基本也没什么权限操作&#xff0c;就不多废话了。 文章概叙 文章从MongoDB是否可以用偶数…

Redis - php通过ssh方式连接到redis服务器

1.应用场景 主要用于使用php通过ssh方式连接到redis服务器&#xff0c;进行一些操作. 2.学习/操作 1.文档阅读 chatgpt & 其他资料 SSH - 学习与实践探究_ssh应用场景 2.整理输出 2.1 是什么 TBD 2.2 为什么需要「应用场景」 TBD 2.3 什么时候出现「历史发展」 TBD 2.4 …

解决mac系统终端无法使用vpn

解决mac系统终端无法使用vpn 换了公司新电脑&#xff0c;以前用vpn都是直接都可以访问&#xff0c;这次换了电脑和vpn&#xff08;这里用的海豚湾&#xff09;就发现访问不了huggingface.co了&#xff0c;无法git clone 下载大模型真的很难受。 解决方法&#xff1a; 查看自…

Maven Eclipse

Eclipse 提供了一个很好的插件 m2eclipse &#xff0c;该插件能将 Maven 和 Eclipse 集成在一起。 在最新的 Eclipse 中自带了 Maven&#xff0c;我们打开&#xff0c;Windows->Preferences&#xff0c;如果会出现下面的画面&#xff1a; 下面列出 m2eclipse 的一些特点&a…

【已编译资料】基于正点原子alpha开发板的第三篇系统移植

系统移植的三大步骤如下&#xff1a; 系统uboot移植系统linux移植系统rootfs制作 一言难尽&#xff0c;踩了不少坑&#xff0c;当时只是想学习驱动开发&#xff0c;发现必须要将第三篇系统移植弄好才可以学习后面驱动&#xff0c;现将移植好的文件分享出来&#xff1a; 仓库&…

第五十二章 学习常用技能 - Global 映射

文章目录 第五十二章 学习常用技能定义数据库定义命名空间Global映射 第五十二章 学习常用技能 定义数据库 创建本地数据库&#xff1a; 登录管理门户。选择系统管理 > 配置 > 系统配置 > 本地数据库。选择创建新数据库以打开数据库向导。输入新数据库的以下信息&a…

libjpeg库

库安装就不说了&#xff0c;我是直接下载编译好的库 Independent JPEG Group (ijg.org) 这个需要自己编译 可以使用的代码 #include <stdio.h> #include <jpeglib.h> #include <stdlib.h>void compress_jpeg(const char* input_filename, const char* outp…

域名,二级域名,局域网自定义域名

什么是域名 域名是互联网上用于标识和定位网站的字符串。它提供了一个易于记忆和使用的方式来访问网站&#xff0c;代替了使用 IP 地址的复杂形式。域名通常由多个部分组成&#xff0c;用点&#xff08;.&#xff09;分隔。 域名的结构是从右向左逐级划分的&#xff0c;每个部…

原理:用UE5制作一个2D游戏

选中资产图片右键--Sprite Actions--Apply Paper2D Texture Settings 制作场景 把它丢到场景里&#xff0c;并把坐标归零 创建图块集tileset 打开新建的tile set&#xff0c;根据最小图块设置最小尺寸单元 选择需要的图块单元&#xff0c;add box 对新建的tile set右键创建til…

UI 自动化测试框架:PO 模式+数据驱动!

1. PO 设计模式简介 什么是 PO 模式&#xff1f; PO&#xff08;PageObject&#xff09;设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成一个 Page 类&#xff0c;并以页面为单位来写测试用例&#xff0c;实现页面对象和测试用例的分离。 PO 模式的设计思想与…

msvcp120.dll是什么东西?找不到msvcp120.dll怎么修复?

在计算机技术的世界里&#xff0c;我们经常会遇到各种错误和问题。其中之一就是MSVCP120.dll的丢失。这是一个非常常见的问题&#xff0c;许多用户在运行某些程序时会遇到这个错误。 MSVCP120.dll是什么&#xff1f; MSVCP120.dll&#xff0c;全名是Microsoft Visual C Runti…