定长内存池的实现

文章介绍了内存池的概念,它通过预先申请内存来提高资源使用效率,减少系统分配和回收的开销。内存池主要解决效率和内存碎片问题。文章以Google的tcmalloc为例,阐述了线程缓存的内存管理,并详细讲解了定长内存池的实现,包括如何使用自由链表管理内存碎片,并通过一个简单的ObjectPool类模板展示了内存池的创建和删除过程。
摘要由CSDN通过智能技术生成

 

 

70530b4346544ad4821252fa57119ccc.jpeg

 


 

文章目录

  • 什么是内存池
    • 池化技术
    • 内存池
    • 内存池主要解决的问题
    • malloc
  • 定长内存池的实现

 前言

        当前项目是实现一个高并发的内存池,他的原型是Google的一个开源项目tcmalloc,tcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free)。

一、什么是内存池

Google开源tcmalloc源码

1.池化技术

池化技术是将程序中需要经常使用的核心资源先申请出 来,放到一个池,由程序源自己管理,这样可以提高资源的使用效率,它可以避免核心资源申请和释放带来的开销,也可以保证本程序占有的资源数量。 经常使用的池化技术包括内存池、线程池和连接池等。

2.内存池

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

3.内存池主要解决的问题

内存池主要解决的当然是效率的问题,其次如果作为系统的内存分配器的角度,还需要解决一下内存碎片的问题。那么什么是内存碎片呢?
 
 
 
再需要补充说明的是内存碎片分为外碎片和内碎片,上面我们讲的外碎片问题。外部碎片是一些空闲的连续内存区域太小,这些内存空间不连续,以至于合计的内存足够,但是不能满足一些的内存分配申请需求。内部碎片是由于一些对齐的需求,导致分配出去的空间中一些内存无法被利用。

4.malloc

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

 

二、定长内存池的实现

 

094fd987416d4b418702b35bf118e501.png

 

6b5c96557c6f46e38411ee9cd0bdb533.png

 

ObjectPool.h

#include <iostream>
#include <time.h>
#include <vector>

using std::cout;
using std::endl;
using std::bad_alloc;

//利用创建二叉树的节点数来衡量效率的方式
struct TreeNode
{
	int val;
	TreeNode* left;
	TreeNode* right;

	TreeNode() :val(0), left(nullptr), right(nullptr)
	{}
};

template<class T>
class ObjectPool
{
public:
	T* New()
	{
		T* obj = nullptr;

		if (_freeList)//如果当前内存中有申请释放的内存碎片,则将会优先使用自由链表中的内存碎片,达到重复利用
		{
			void* next = *((void**)_freeList);//这里使用二级指针的方式来解决在不同平台下的指针的大小不同的问题
			obj = (T*)_freeList;
			_freeList = next;
		}
		else
		{
			if (_remainBytes < sizeof(T))//剩余的内存不够一个对象的话,则重新开大块空间
			{
				_remainBytes = 128 * 1024;
				_memory = (char*)malloc(_remainBytes);
				if (_memory == nullptr)
					throw bad_alloc();
			}

			obj = (T*)_memory;
			size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
			_memory += objSize;
			_remainBytes -= objSize;
		}

		//采用定位new的方式,显示调用T对象的构造函数进行初始化
		new(obj)T;
		return obj;
	}

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

		//这里采用头插法,将已经释放的空间进行管理
		*(void**)obj = _freeList;
		_freeList = obj;
	}
private:
	char* _memory = nullptr;//指向大块内存的指针
	size_t _remainBytes = 0;//大块内存在切分过程中剩余字节数
	void* _freeList=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;
}

 Test.cpp

#include "ObjectPool.h"

int main()
{
	TestObjectPool();
	return 0;
}

//直接去堆上按页申请空间

 windowsLinux下如何直接向堆申请页为单位的大块内存:

VirtualAlloc

brk和mmap

ObjectPool.h

#include <iostream>
#include <time.h>
#include <vector>

using std::cout;
using std::endl;
using std::bad_alloc;

#ifdef _WIN32
	#include<Windows.h>
#else
	
#endif


inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	void* ptr = VirtualAlloc(0, kpage * (1 << 12), 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 (_remainBytes < sizeof(T))//剩余的内存不够一个对象的话,则重新开大块空间
			{
				_remainBytes = 128 * 1024;
				_memory = (char*)malloc(_remainBytes);
				if (_memory == nullptr)
					throw bad_alloc();
			}

			obj = (T*)_memory;
			size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
			_memory += objSize;
			_remainBytes -= objSize;
		}

		//采用定位new的方式,显示调用T对象的构造函数进行初始化
		new(obj)T;
		return obj;
	}

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

		//这里采用头插法,将已经释放的空间进行管理
		*(void**)obj = _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;
}

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

‘(尐儍苽-℡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值