定长内存池的实现

时间:2021-08-16 01:06:36

 

定长内存池的实现


文章目录

  • 什么是内存池
    • 池化技术
    • 内存池
    • 内存池主要解决的问题
    • 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。

 

二、定长内存池的实现

定长内存池的实现

 

定长内存池的实现

 

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;
}