项目日记<二> 定长内存池的实现

        malloc其实就是一个通用的内存池,在什么场景下都可以使用,但这也意味着malloc在什么场景下都不会有很高的性能,因为malloc并不是针对某种场景专门设计的。

        定长内存池就是针对固定大小内存块的申请和释放的内存池,由于定长内存池只需要支持固定大小内存块的申请和释放,因此我们可以将其性能做到极致,并且在实现定长内存池时不需要考虑内存碎片等问题,因为我们申请/释放的都是固定大小的内存块。

        我们可以通过实现定长内存池来熟悉一下对简单内存池的控制,其次,这个定长内存池后面会作为高并发内存池的一个基础组件。

定长内存分配器: 

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

每个固定内存分配器里面有两个链表,OpenList 用于存储未分配的空闲 对象,CloseList用于存储已分配的内存对象,那么所谓的分配就是从 OpenList 中取出一个对象 放到 CloseList 里并且返回给用户,释放又是从 CloseList 移回到 OpenList。分配时如果不够, 那么就需要增长 OpenList:申请一个大一点的内存块,切割成比如 64 个相同大小的对象添加到 OpenList中。这个固定内存分配器回收的时候,统一把先前向系统申请的内存块全部还给系统。 

  • 优点:简单粗暴,200行代码就可以搞定,分配和释放的效率高,解决实际中特定场景下的问题 有效。
  • 缺点:功能单一,只能解决定长的内存需求,另外占着内存没有释放。

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

  • _memory:先向操作系统申请一块足够大的空间,我们需要一个指针来指向这块空间。
  • _freelist:程序释放空间时,我们不用还给操作系统,而是用自由链表来连接起来,当下次申请空间时,先在自由链表中找,我们需要一个指针来指向链表的头部。
  • _remainBytes:表示_memory所指向的内存所剩的大小。

定长池基础框架 :

//定长内存池
template<class T>
class ObjectPool
{
public:
	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;
	}
	T* New()
	{}

	void Delete(T* obj)
	{}

private:
	char* _memory = nullptr;     //向系统申请的一大块内存,申请了多少字节就从起始地址++多少次,所以定义为char*
	void* _freeList = nullptr;   //还回来的空间的过程中,链接的自由链表的头指针
	size_t _surplusBytes = 0;    //定长池剩余的空间大小(字节为单位)
};

自由链表:

如何把释放回来的内存块连接起来?
我们在这些内存块的头4或者8个字节中存放下一个内存块的指针。

T* obj = nullptr;

// 优先把还回来内存块对象,再次重复利用
if (_freeList)
{
    //在32位下解引用拿到4字节,64位下解引用拿到8字节。
	void* next = *((void**)_freeList);
	obj = (T*)_freeList;
	_freeList = next;
}

void* next = * (void**)_freeList;这段代码的意思就是将自由链表的头节点的下一个节点提取出来,由于使用的机器是32还是64位是不确定的,所以将freelist强转为void**后,再解引用就是void* 类型的变量,假如你是32位那么void* 就占四位,假如你是64位那么void* 就占八位,完全省略了去判断电脑是多少位的必要.并且将自由链表中的数据返还给外界使用是用的头删的方法,因为效率更快!

 

 直接切分内存部分:

假如在64位机器下,一个指针的大小是8字节,但是用户申请了4字节的空间,这时我们需要将空间扩为八字节再返回给用户,因为一旦这份空间被返回,四字节是无法当成指针使用存储下一个节点地址的!并且切分内存后,_memory要向后移动,避免将同一份内存切分给不同的变量.在申请完内存后,需要使用定位new来对已经开辟好的空间做初始化.

new:

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);
				_memory = (char*)SystemAlloc(_remainBytes >> 13);
				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;
		}

		// 定位new,显示调用T的构造函数初始化
		new(obj)T;

		return obj;
	}

定长内存池的物理结剖析:

将已经释放的空间挂在自由链表上是逻辑的结构,但是实际的物理结构是不管自由链表悬挂了多少个内存块,它们实际上都是连在一起的在定长池中的,只不过已经被使用了的空间在指针_memory前面,而未分配内存的空间在_memory后面! 

 

 

Delete:

定长池的删除比较简单,不需要将空间free掉,只需要将空间挂到自由链表上即可,并且使用头插的方式

	void Delete(T* obj)
	{
		// 显示调用析构函数清理对象
		obj->~T();
	//使用**强转,不管是32位还是64位都没问题
	//取obj对象头四个字节来存储nullptr或下一个被Delete的对象的空间的地址
	//使用头插,不用每次都去找尾
		*(void**)obj = _freeList;
		_freeList = obj;
	}

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值