设计高并发内存池申请流程

本文详细介绍了如何实现线程缓存,包括对象映射到桶的算法、使用TLS技术以及线程缓存的申请和释放过程。通过优化内存分配策略,控制内存碎片在1%-12%之间,提高内存利用率。同时,文章还涵盖了中心缓存和页缓存的结构,以及如何在多线程环境下高效地分配和回收内存。
摘要由CSDN通过智能技术生成

实现线程缓存

分析完整体架构我们来从最底层实现。
在这里插入图片描述

取下一个对象

之前提到这里使用头四个字节来存下一个对象的。适用于32位以及64位
在这里插入图片描述
在这里插入图片描述

头插与头删

将对象头插与头删
在这里插入图片描述
这样就可以实现从自由链表中放对象,拿对象。

和STL空间配置器结构类似,多个不同大小的对象,所以用一个对象数组。
假如我们最大也是128个字节,以8为间隔,那么数组长度就是16
在这里插入图片描述

对象映射到桶

现在来一个对象,怎么算他在那个桶呢?

在这里插入图片描述

TLS技术

在这里插入图片描述
在这里插入图片描述

测试

在这里插入图片描述

公共头文件

#pragma once

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

#include <thread>

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

inline void*& NextObj(void* obj)
{
	return *((void**)obj);
}

//size_t Index(size_t size)
//{
//	if (size % 8 == 0)
//	{
//		return size / 8 - 1;
//	}
//	else
//	{
//		return size / 8;
//	}
//}

// 8 + 7 = 15
// 7 + 7
// ...
// 1 + 7 = 8
static size_t Index(size_t size)
{
	return ((size + (2^3 - 1)) >> 3) - 1;
}

class FreeList
{
public:
	// Í·²å
	void Push(void* obj)
	{
		NextObj(obj) = _head;
		_head = obj;
	}

	// ͷɾ
	void* Pop()
	{
		void* obj = _head;
		_head = NextObj(_head);

		return obj;
	}

	bool Empty()
	{
		return _head == nullptr;
	}

private:
	void* _head = nullptr;
};
  
#pragma once

#include "Common.h"

class ThreadCache
{
public:

	void* Allocate(size_t size);
	void Deallocate(void* ptr, size_t size);
private:
	FreeList _freeLists[16];
};

static __declspec(thread) ThreadCache* tls_threadcache = nullptr;
#include "ThreadCache.h"

void* ThreadCache::Allocate(size_t size)
{
	size_t i = Index(size);
	if (!_freeLists[i].Empty())
	{
		//return _freeLists[i].Pop();
	}
	else
	{
		// ...
	}

	return nullptr;
}

void ThreadCache::Deallocate(void* ptr, size_t size)
{
	size_t i = Index(size);
	_freeLists[i].Push(ptr);

}

遇到个错误
在这里插入图片描述

调用顺序

#pragma once

#include "Common.h"
#include "ThreadCache.h"

//void* tcmalloc(size_t size)
void* ConcurrentAlloc(size_t size)
{
	if (tls_threadcache == nullptr)
	{
		tls_threadcache = new ThreadCache;
	}

	cout << tls_threadcache << endl;

	return tls_threadcache->Allocate(size);
}

void ConcurrentFree(void* ptr, size_t size)
{
	assert(tls_threadcache);

	tls_threadcache->Deallocate(ptr, size);
}

在这里插入图片描述

更改代码

在这里插入图片描述

定义线程缓存结构

在这里插入图片描述
在这里插入图片描述
使内存碎片始终控制在1%-12%

在这里插入图片描述


	//辅助计算thread_Cache中各个freeList的映射,central_cache中各个Spanlist的映射
	static size_t _Index(size_t bytes,size_t align_shift)
	{    
		//[1,128]区间,8字节对齐,例如分配1字节,最小8字节
		        //((1    +  (2^3)-1)/8 )   - 1
		        //算出本区间桶的相对位置
		//[129,1024]区间,16字节对齐,例如分配129字节(129先减去前面的128),最小144字节,但此时应该减去前128个字节按照16字节的对齐方式计算
		        //(1   +   (2^4)-1)/16)     -1
		        //算出本区间桶的相对位置
		return ((bytes + (1<<align_shift)-1)>>align_shift) - 1;
	}
	    // 控制在平均1%-12%左右的内碎片浪费
		// [1,128] 8byte对齐 freelist[0,15]
		// [129,1024] 16byte对齐 freelist[16,71]
		// [1025,8*1024] 128byte对齐 freelist[72,127]
		// [8*1024+1,64*1024] 1024byte对齐 freelist[128,183]
	
	//算出他们在那个桶
	static size_t Index(size_t bytes)
	{
		assert(bytes <= MAXBYTES);

		//每个区间桶的数量
		static int group[4] = { 16, 56, 56, 56 };

		//8字节对齐
		if (bytes <= 128)
		{
			return _Index(bytes, 3);
		}//16字节对齐
		else if (bytes <= 1024)
		{
			        //由于返回的是本区间桶的相对位置,所以加上上个区间的所有桶,就是整体的绝对位置
			       
			return _Index(bytes - 128, 4)+group[0];
		}//128字节对齐
		else if (bytes <= (8 * 1024))
		{
			return _Index(bytes - 1024, 7) + group[0] + group[1];
		}//1024字节对齐
		else if (bytes <= (64 * 1024))
		{
			return _Index(bytes - (8 * 1024), 10) + group[0] + group[1] + group[2];
		}
		assert(false);
		return -1;
	}

线程缓存申请

在这里插入图片描述
在这里插入图片描述

//申请内存
void* ThreadCache::Allocate(size_t size)
{
	size_t i = SizeClass::Index(size);
	//对应的桶非空,取一个对象弹出去
	if (!_freelist[i].Empty())
	{
		return _freelist[i].Pop();
	}
	else//没空间,去向central cache要
	{
		//两种情况1.中心缓存也没有2.中心缓存有(其他线程空闲的较多还给中心缓存,从pagecache切分而来)
		return FetchFromCentralCache(i, size);
	}
}

定义中心缓存结构

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
定义Spnalist将Span连接起来

在这里插入图片描述

中心缓存申请内存

这个是假如线程缓存没有对象的话,向中心缓存拿一批对象的接口
在这里插入图片描述

size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t n, size_t size)
{
	size_t i = SizeClass::Index(size);
	Span* span = GetOneSpan(_spanLists[i], size);

	// 找到一个有对象的span,有多少给多少
	size_t j = 1;
	start = span->_list;
	void* cur = start;
	void* prev = start;
	while (j <= n && cur != nullptr)
	{
		prev = cur;
		cur = NextObj(cur);
		++j;
		span->_usecount++;
	}

	span->_list = cur;
	end = prev;
	NextObj(prev) = nullptr;

	return j-1;
}

可以看到里面的GetSpan是获取一个Span对象
在这里插入图片描述
在这里插入图片描述

页缓存结构

看一下之前的两种映射
在这里插入图片描述
在这里插入图片描述
第一次过来会向系统申请128页的大Span。根据中心缓存所需进行切分
在这里插入图片描述

#include "PageCache.h"

// 向系统申请k页内存
void* PageCache::SystemAllocPage(size_t k)
{
	return ::SystemAlloc(k);
}

Span* PageCache::NewSpan(size_t k)
{
	if (!_spanList[k].Empty())
	{
		Span* it = _spanList[k].Begin();
		_spanList[k].Erase(it);
		return it;
	}

	for (size_t i = k+1; i < NPAGES; ++i)
	{
		// 大页给切小,切成k页的span返回
		// 切出i-k页挂回自由链表
		if (!_spanList[i].Empty())
		{
			Span* span = _spanList[i].Begin();
			_spanList->Erase(span);

			Span* splitSpan = new Span;
			splitSpan->_pageId = span->_pageId + k;
			splitSpan->_n = span->_n - k;

			span->_n = k;

			_spanList[splitSpan->_n].Insert(_spanList[splitSpan->_n].Begin(), splitSpan);

			return span;
		}
	}

	Span* bigSpan = new Span;
	void* memory = SystemAllocPage(NPAGES - 1);
	bigSpan->_pageId = (size_t)memory >> 12;
	bigSpan->_n = NPAGES - 1;

	_spanList[NPAGES - 1].Insert(_spanList[NPAGES - 1].Begin(), bigSpan);

	return NewSpan(k);
}

总结

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

楠c

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

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

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

打赏作者

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

抵扣说明:

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

余额充值