iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++如何实现定长内存池详解
  • 251
分享到

C++如何实现定长内存池详解

2024-04-02 19:04:59 251人浏览 薄情痞子
摘要

目录1. 池化技术2. 内存池概念2.1 内存碎片3. 实现定长内存池3.1 定位new表达式(placement-new)3.2 完整实现总结1. 池化技术 池是在计算机技术中经

1. 池化技术

池是在计算机技术中经常使用的一种设计模式,其内涵在于:将程序中需要经常使用的核心资源先申请出来,放到一个池内,由程序自己管理,这样可以提高资源的使用效率,也可以保证本程序占有的资源数量。 经常使用的池技术包括内存池、线程池和连接池(数据库经常使用到)等,其中尤以内存池和线程池使用最多。

2. 内存池概念

内存池(Memory Pool) 是一种动态内存分配与管理技术。 通常情况下,程序员习惯直接使用 new、delete、malloc、free 等api申请分配和释放内存,这样导致的后果是:当程序长时间运行时,由于所申请内存块的大小不定,频繁使用时会造成大量的内存碎片从而降低程序和操作系统的性能。内存池则是在真正使用内存之前,先申请分配一大块内存(内存池)留作备用,当程序员申请内存时,从池中取出一块动态分配,当程序员释放内存时,将释放的内存再放入池内,再次申请池可以 再取出来使用,并尽量与周边的空闲内存块合并。若内存池不够时,则自动扩大内存池,从操作系统中申请更大的内存池。

2.1 内存碎片

  • 内碎片:

内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间;内部碎片是处于区域内部或页面内部的存储块。占有这些区域或页面的进程并不使用这个 存储块。而在进程占有这块存储块时,系统无法利用它。直到进程释放它,或进程结束时,系统才有可能利用这个存储块。(编译器会对数据进行对齐操作,当不是编译器的最小对齐数的整数倍的时候需要添加一些来保证对齐,那么这块为了对齐而添加的就是内碎片)

  • 外碎片(通常所讲的内存碎片):

假设系统依次分配了16byte、8byte、16byte、4byte,还剩余8byte未分配。这时要分配一个24byte的空间,操作系统回收了一个上面的两个16byte,总的剩余空间有40byte,但是却不能分配出一个连续24byte的空间,这就是外碎片问题。(本来有足够的内存,但是由于碎片化无法申请到稍大一些的连续内存)

3. 实现定长内存池

3.1 定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。使用格式:new (place_address) type或者new (place_address) type(initializer-list),place_address必须是一个指针,initializer-list是类型的初始化列表。
使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要**使用new的定义表达式进行显示调构造函数**进行初始化。

3.2 完整实现

即实现一个 FreeList,每个 FreeList 用于分配固定大小的内存块,比如用于分配 32字节对象的固定内存分配器,之类的。

优点:
简单粗暴,分配和释放的效率高,解决实际中特定场景下的问题有效。

缺点:
功能单一,只能解决定长的内存需求,另外占着内存没有释放。

实现的思想:

  1. 先向内存申请一块大的内存,如果需要,那么就对这块已经申请出来的内存进行切割(减少了和操作系统底层打交道的次数,效率也就提高了,内存池一定是可以解决申请和释放内存的效率的)
  2. 对于不需要的小块内存,并不是将其进行释放掉,而是使用一个freeList将他们管理起来,如果freeList中有了空余的,那么再次申请内存首先会到自由链表中取,而不是去申请出来的大内存块进行切割
  3. 对于这个申请出来的小块内存,前4个或者8个字节存放的是下一个小内存块的地址(这是由于在32位平台下指针的大小是4字节,在64位平台下指针则是8字节),这里如何巧妙的进行平台下指针大小的适配,需要好好的进行琢磨。
  4. (帮助理解3)指针就是地址,那么指针的类型是为了解引用取到大小,对于所申请出来的内存的类型我是不关心的,在32位平台下我就想取出他的前4个字节,然后存放我的下一个小内存的地址,所以把obj强转为int*类型,然后在解引用就可以拿到前4个字节。那如果在64位平台下,就应该取其前8个字节来存放下一个小内存的地址,但是如果都写为取前4个字节的话,这里就会发生指针越界的问题。下述代码所写的Nextobj()接口函数就是为了能够取出小内存中的前4个字节或者8个字节。我需要的类型是void*,可以自动的适配平台(类比于上述的int类型,就可以相通)

//实现一个定长的内存池(针对某一个具体的对象,所以起名字叫ObjictPool)
#pragma once 

#include"Common.h"

template<class T>
class ObjectPool
{
public:
	~ObjectPool()
	{
		//
	}
	//此时代码还存一个很大的问题:我们默认这里取的是前四个字节,但是在64位的平台下,需要取的应该是这块小内存的前8个字节来保存地址
	void*& Nextobj(void* obj)
	{
		return *((void**)obj); //对于返回的void*可以自动的适配平台
	}
	//申请内存的函数接口
	T* New()
	{
		T* obj = nullptr;
		//一上来首先应该判断freeList
		if (_freeList)
		{
			//那就直接从自由链表中取一块出来
			obj = (T*)_freeList;
			//_freeList = (void*)(*(int*)_freeList);
			_freeList = Nextobj(_freeList);
		}
		else
		{
			//表示自由链表是空的
			//那么这里又要进行判断,memory有没有
			if (_leftSize < sizeof(T)) //说明此时空间不够了
			{
				//那么就进行切割
				_leftSize = 1024 * 100;
				_memory = (char*)malloc(_leftSize);
				//对于c++来说,如果向系统申请失败了,则会抛异常
				if (_memory == nullptr)
				{
					throw std::bad_alloc();
				}
			}
			//进行memory的切割
			obj = (T*)_memory;
			_memory += sizeof(T); //这里如果想不通可以画一下图,很简单
			_leftSize -= sizeof(T); //表示剩余空间的大小
		}
		new(obj)T;  //定位new,因为刚申请的空间内如果是自定义类型是没有初始化的
		//所以需要可以显示的调用这个类型的构造函数,这个是专门配合内存池使用的
		return obj;
	}

	void Delete(T* obj)
	{
		obj->~T();//先把自定义类型进行析构
		//然后在进行释放,但是此时还回来的都是一块一块的小内存,无法做到一次性进行free,所以需要一个自由链表将这些小内存都挂接住
		//这里其实才是核心的关键点
		//对于指针来说,在32位的平台下面是4字节,在64位平台下面是8字节

		//头插到freeList
		//*((int*)obj)= (int)_freeList;
		Nextobj(obj) = _freeList;
		_freeList = obj;
	}
private:
	char* _memory = nullptr;//这里给char*是为了好走大小,并不是一定要给T*或者void*
	int _leftSize = 0; //为什么会加入这个成员变量呢?因为你的menory += sizeof(T),有可能就会造成越界的问题
	void* _freeList = nullptr; //给一些缺省值,让他的构造函数自己生成就可以了
};

struct Treenode
{
	int _val;
	TreeNode* _left;
	TreeNode* _right;

	TreeNode()
		:_val(0)
		, _left(nullptr)
		, _right(nullptr)
	{}
};
void TestObjectPool()
{

	验证还回来的内存是否重复利用的问题
	ObjectPool<TreeNode> tnPool;
	TreeNode* node1 = tnPool.New();
	TreeNode* node2 = tnPool.New();
	cout << node1 << endl;
	cout << node2 << endl;

	tnPool.Delete(node1);
	TreeNode* node3 = tnPool.New();
	cout << node3 << endl;

	cout << endl;

	//验证内存池到底快不快,有没有做到性能的优化
	//new底层本身调用的malloc,会一直和操作系统的底部打交道
	size_t begin1 = clock();
	std::vector<TreeNode*> v1;
	for (int i = 0; i < 1000000; ++i)
	{
		v1.push_back(new TreeNode);
	}
	for (int i = 0; i < 1000000; ++i)
	{
		delete v1[i];
	}
	size_t end1 = clock();


	//这里我们调用自己所写的内存池
	ObjectPool<TreeNode> tnPool;
	size_t begin2 = clock();
	std::vector<TreeNode*> v2;
	for (int i = 0; i < 1000000; ++i)
	{
		v2.push_back(tnPool.New());
	}
	for (int i = 0; i < 1000000; ++i)
	{
		tnPool.Delete(v2[i]);
	}
	size_t end2 = clock();

	cout << end1 - begin1 << endl;
	cout << end2 - begin2 << endl;
}

这个定长的内存池依旧存在着大量的问题:

  1. 我们所采用的是取这块小内存的前4个或者8个字节来存放下一个小内存的地址,但是如果这里的模板类型T是一个char类型怎么办,它本身都没有4字节,怎么来存放?(解决的办法就是,进行一次判断如果sizeof(T) < sizeof(T*)的大小,那么就开辟T*的大小)
  2. 无法编写这个ObjectPool的析构函数,因为申请的都是一个个的小块内存,但是对于free来说,应该是一次性的对整个所开辟出来的内存块都进行释放(解决的办法就是,将这些向操作系统申请的大块内存也管理起来,如果小块内存都还回来了,那么就可以对这个大块内存进行释放)

对于上述的具体实现可以参考下面这篇文章写的很详细:

如何设计一个简单内存池

总结

到此这篇关于C++如何实现定长内存池的文章就介绍到这了,更多相关C++定长内存池内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++如何实现定长内存池详解

本文链接: https://www.lsjlt.com/news/136231.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++如何实现定长内存池详解
    目录1. 池化技术2. 内存池概念2.1 内存碎片3. 实现定长内存池3.1 定位new表达式(placement-new)3.2 完整实现总结1. 池化技术 池是在计算机技术中经...
    99+
    2024-04-02
  • 详解利用C语言如何实现简单的内存池
    前言 在编程过程中,尤其是对于C语言开发者,其实编程就是在使用内存,不停地变化内存中的数据。当我们想开辟一片新的内存使用时,就会使用malloc实现。但是通过查阅很多资料,发现频繁的...
    99+
    2024-04-02
  • C++中高性能内存池的实现详解
    目录一、概述二、主函数设计三、模板链表栈四、设计内存池五、实现六、与 std::vector 的性能对比总结一、概述 在 C/C++ 中,内存管理是一个非常棘手的问题,我们在编写一个...
    99+
    2022-11-13
    C++高性能内存池 C++ 内存池
  • nginx内存池如何实现
    这篇文章主要讲解了“nginx内存池如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nginx内存池如何实现”吧!一、简介最新稳定版本nginx1.20.2。为了能高效、快速的分配内存...
    99+
    2023-07-02
  • C++中内存池的简单原理及实现详解
    目录为什么要用内存池内存池原理内存池设计内存池实现为什么要用内存池 C++程序默认的内存管理(new,delete,malloc,free)会频繁地在堆上分配和释放内存,导致性能的损...
    99+
    2023-03-01
    C++内存池原理 C++实现内存池 C++内存池
  • C语言如何实现简单的内存池
    本篇内容主要讲解“C语言如何实现简单的内存池”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言如何实现简单的内存池”吧!前言在编程过程中,尤其是对于C语言开发者,其实编程就是在使用内存,不停地...
    99+
    2023-06-20
  • C++内存池的实现方法
    这篇文章主要讲解了“C++内存池的实现方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++内存池的实现方法”吧!目录一、内存池基础知识什么是内存池1 池化技术2 内存池内存池的作用1 效...
    99+
    2023-06-20
  • C++内存池的简单实现
    目录一、内存池基础知识1、什么是内存池1.1 池化技术1.2 内存池2、内存池的作用2.1 效率问题2.2 内存碎片3、内存池技术的演进二、简易内存池原理1、整体设计1.1 内存池结...
    99+
    2024-04-02
  • C++手写内存池的案例详解
    引言 使用new expression为类的多个实例分配动态内存时,cookie导致内存利用率可能不高,此时我们通过实现类的内存池来降低overhead。从不成熟到巧妙优化的内存池,...
    99+
    2024-04-02
  • C++基本组件之内存池详解
    内存池概念 1:尽量减少malloc的次数 2:频繁申请小块内存空间都造成空间的极大浪费 3:利用new和delete运算符重载,替代系统调用 4:减少malloc的次数,可在一定程...
    99+
    2023-03-01
    C++ 基本组件 内存池 C++ 内存池
  • C++内存管理之简易内存池的实现
    目录什么是内存池?它的实现过程为:初步实现使用嵌入指针改进更简化:static allocatormacor for static allocator什么是内存池? 频繁的调用 ma...
    99+
    2024-04-02
  • C++高并发内存池的实现
    目录项目介绍内存池介绍定长内存池的实现高并发内存池整体框架设计threadcachethreadcache整体设计threadcache哈希桶映射对齐规则threadcacheTLS...
    99+
    2024-04-02
  • C++内存管理中简易内存池怎么实现
    这篇文章主要介绍“C++内存管理中简易内存池怎么实现”,在日常操作中,相信很多人在C++内存管理中简易内存池怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++内存管理中简易内存池怎么实现”的疑惑有所...
    99+
    2023-06-22
  • C/C++实现内存泄漏检测详解
    目录内存泄漏的两个问题使用宏定义覆盖 malloc 和 free 函数使用 hook 钩子内存泄漏的两个问题 是否有内存泄漏?内存泄漏是在代码的哪一行? 检测内存泄漏主要从上面两个问...
    99+
    2023-02-09
    C++内存泄漏检测 C++内存泄漏
  • C++内存对齐如何实现
    本篇内容介绍了“C++内存对齐如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!内存对齐的基本原则:结构(struct/class)的内...
    99+
    2023-07-05
  • 使用 C++ 函数的内存分配和销毁来实现内存池
    c++++ 函数的内存分配和销毁可用于实现内存池,从而提高性能。内存池预先分配内存块并重复使用,避免了频繁的系统分配和释放操作。可定义内存分配函数 poolallocate 和内存销毁函...
    99+
    2024-04-22
    c++ 内存池
  • Python内存管理器如何实现池化技术
    目录前言内存层次结构内存管理逻辑内存布局及对应的数据结构内存分配内存释放总结前言 Python 中一切皆对象,这些对象的内存都是在运行时动态地在堆中进行分配的,就连 Python 虚...
    99+
    2024-04-02
  • 详解LeakCanary分析内存泄露如何实现
    目录前言LeakCanary的使用LeakCanary原理源码浅析初始化使用总结前言 平时我们都有用到LeakCanary来分析内存泄露的情况,这里可以来看看LeakCanary是如...
    99+
    2022-12-08
    LeakCanary分析内存泄露 LeakCanary 内存泄露
  • C++如何实现对象池
    这篇“C++如何实现对象池”文章,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要参考一下,对于“C++如何实现对象池”,小编整理了以下知识点,请大家跟着小编的步伐一步一步的慢慢理解,接下来就让我们进入主题吧。前言需求...
    99+
    2023-06-26
  • Java中线程池自定义实现详解
    目录前言线程为什么不能多次调用start方法线程池到底是如何复用的前言 最初使用线程池的时候,网上的文章告诉我说线程池可以线程复用,提高线程的创建效率。从此我的脑海中便为线程池打上了...
    99+
    2023-03-01
    Java线程池自定义 Java线程池
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作