C++项目——高并发内存池(6)--申请大块内存&&摆脱new操作符&&释放函数参数的优化

本文详细介绍了C++内存池优化策略,针对大于32页且小于等于128页及大于128页的大块内存申请进行了特殊处理,避免了对new操作符的依赖,并探讨了ConcurrentFree()函数的优化,通过在Span类中增加_objSize变量来存储对象大小,从而简化释放内存时的参数需求。
摘要由CSDN通过智能技术生成

1.申请大块内存

1.1  32页<size<=128页

由于ThreadCache管理不了超过128K(即32页)的空间,所以在申请空间函数调用的时候,进行特殊处理,直接向PageCache要即可。

static void* ConcurrentAlloc(size_t size)
{
	if (size > MAX_BYTES)//大于32页 ThreadCache管理不了 直接向PageCache要
	{
		//算对齐数
		size_t alignSize = SizeClass::RoundUp(size);
		//算一下需要多少页
		size_t kpage = alignSize >> PAGE_SHIFT;

		PageCache::GetInstance()->_pageMtx.lock();
		Span* span = PageCache::GetInstance()->NewSpan(kpage);
		PageCache::GetInstance()->_pageMtx.unlock();
		//计算该内存的起始地址
		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		return ptr;
	}
	else
	{
		//通过ThreadCache管理
	}
}

1.2 size>128页

在NewSpan()中进行特判处理,直接向堆去申请。

Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0);

	if (k > NPAGES - 1)
	{
		void* ptr = SystemAlloc(k);
		//Span* span = new Span;
		Span* span = _spanPool.New();
		span->_n = k;
		span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;

		_idSpanMap[span->_pageId] = span;
		return span;
	}
    //下面是正常逻辑 之前写过了
    //...
}

2.脱离new操作符

高并发内存池实现的目的初衷就是为了避免每次申请空间都去调用malloc,但是可以发现我们在实现的过程中依然有使用new操作符,比如在一个大页的span分裂成两个span时,需要我们new出来一个Span对象的指针,以便操作。那要如何避免这些地方使用new操作符呢?

定长内存池,在本项目的开始就实现了一个定长内存池,他是针对于一个模板类型的内存池,而我们整个项目中,只有Span类的对象需要我们调用new,和TLS时线程获取自己的专属的ThreadCache对象所以可以满足我们的需求。

  • 所有的new操作改成定长内存池的形式

  •  delete也要改成定长内存池的形式

  •  还有在TLS中,每个线程无锁的获取自己的专属的ThreadCache对象中,我们也使用了new

 

 3.ConcurrentFree()的优化

原本的函数定义如下

void ConcurrentFree(void* ptr, size_t size)

 当我们释放一块内存的时候,不仅需要传入指针,还需要传入这块地址的大小。有时申请和释放会相隔比较远,不好确切的得到当时申请的字节数,所以需要进行优化,将这个封装好的释放调用减少一个参数,即只需要传入地址即可。

3.1 优化思路

由地址,我们可以知道页号,有了页号就可以通过_idSpanMap里面找到对应的Span,所以我们可以尝试在Span类里面再新增一个变量_objSize,用来存放这个Span会被切成的字节大小。

struct Span
{
	PAGE_ID _pageId=0;// 大块内存起始页的页号
	size_t _n=0;//数量
	Span* _next=nullptr;
	Span* _prev=nullptr;

	size_t _objSize = 0;//该span被切成小内存块的大小
	size_t _useCount=0;//被使用的个数
	void* _freeList=nullptr;//切好的小块内存的自由链表
	bool _isUse = false;  
};

3.2 _objSize的初始化场景

那_objSize在什么时间初始化呢?依然从程序刚开始时进行推演,当第一次内存申请时

一、申请的内存空间小于等于32页时,该内存空间可以由ThreadCache管理,第一次申请时会调用NewSpan()函数,申请一个128字节的页块,然后将被申请的kSpan返回给central cache使用,所以我们只需要在NewSpan()函数返回的时候设置即可。

二、申请的内存空间大于128页,在申请大块空间的时候进行处理。

 3.3 函数实现

static void ConcurrentFree(void* ptr)
{
	Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);
	size_t size = span->_objSize;
	if (size > MAX_BYTES)
	{
		PageCache::GetInstance()->_pageMtx.lock();
		PageCache::GetInstance()->ReleaseSpanToPageCache(span);
		PageCache::GetInstance()->_pageMtx.unlock();
	}
	else
	{
		assert(pTLSThreadCache);
		pTLSThreadCache->Deallocate(ptr, size);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值