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

Posted 楠c

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计高并发内存池申请流程相关的知识,希望对你有一定的参考价值。

实现线程缓存

分析完整体架构我们来从最底层实现。

取下一个对象

之前提到这里使用头四个字节来存下一个对象的。适用于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);

总结

以上是关于设计高并发内存池申请流程的主要内容,如果未能解决你的问题,请参考以下文章

实战项目高并发内存池

高并发内存池——基于Google开源项目tcmalloc的简洁实现

高并发内存池——基于Google开源项目tcmalloc的简洁实现

高并发内存池——基于Google开源项目tcmalloc的简洁实现

C++实现的高并发内存池

C++实现的高并发内存池