高并发内存池

"花,就在火海里摇曳开着。"


一、技术介绍

(1)什么是池化技术? 

是在计算机技术中经常使用的一种设计模式 ,其内涵在于:将程序中需要经常使用的核心资源先申请出来,放到一个池内,由程序自己管理。
这样可以提高资源的使用效率(不用频繁向系统申请、释放资源),也可以保证本程 序占有的资源数量

 

(2)内存池

内存池(Memory Pool) 是一种动态内存分配与管理技术。
通常情况下,程序员习惯直接使用 new、delete、malloc、free等API(封装函数)申请分配和释放内存,这样导致的后果是:当程序长时间 运行时,由于所申请内存块的大小不定, 频繁使用时会造成大量的内存碎片从而降低程序和操作系统的性能
内存池则是在真正使用内存之前:
①先申请分配一大块内存(内存池)留作备用, 当程序 员申请内存时,从池中取出一块动态分配,当程序员释放内存时,将释放的内存再放入池内,再 次申请池可以 再取出来使用。
②解决内存碎片尽量与周边的空闲内存块合
③若内存池不够时,则自动扩大内存池,从操作系统中申请更大的内存池。

如何理解内存碎片问题?

假设系统依次分配了16byte、8byte、16byte、4byte,还剩余8byte未分配。这时要分配一个
24byte的空间,操作系统回收了一个上面的两个16byte,总的剩余空间有40byte,但是却不能分
配出一个连续24byte的空间,这就是内存碎片问题。

外碎片:申请内存不连续的问题

内碎片:对齐方式导致的空间浪费 


二、小试牛刀

        在开始tcmalloc之前,还是先上一道"开胃菜"——定长内存池分配器

所谓定长内存池,就是在申请资源之前,已经将空间开好了。 而不是直接向系统要内存块。

即:实现一个memory指向一大块申请好的内存池。 同时用freelist用于管理释放 回来的小块内存。

定长内存池的优点很明显:实现简单,分配和释放效率都达到了极致

不足点在于:功能单一,只能够解决定长的需求。同时占用着内存空间。

(1)类设计  

  

定长内存池的可以是上面的两种。这里我就选取第二种。

注:这里设计指向大块内存

(2)申请空间 

但是其中仍然没有脱离malloc,即便malloc也有自己的内存池。在WINDOWS下 有自己专门向堆申请空间的系统调用函数。

static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	//virtualAlloc	            //转换为字节
	void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	//linux _brk
#endif

	if (ptr == nullptr)
	{
		throw std::bad_alloc();
	}
	return ptr;
}

    T* New()
	{
		T* obj = nullptr;
		if (_FreeList)
		{
			void* nextObj = *((void**)_FreeList);
			obj = (T*)_FreeList;
			_FreeList = nextObj;
		}
		else
		{
			if (_RemainSize < sizeof(T))
			{
				_RemainSize = 128 * 1024;
				//void* ptr = malloc(_RemainSize);
				//128 / 8 
				void* ptr = SystemAlloc(16);
				if (ptr == nullptr)
				{
					throw std::bad_alloc();
				}
				_Memory =(char*)ptr;
			}
			size_t ObjectSize = sizeof(T*) > sizeof(T) ? sizeof(T*) : sizeof(T);
			obj =(T*) _Memory;
			_Memory += ObjectSize;
			_RemainSize -= ObjectSize;
		}

		new(obj) T;
		return obj;
	}

(3)释放空间

释放空间更为简单 就只是做做简单的头插! 

	void Delete(T* obj)
	{
		obj->~T();

		*(void**)obj = _FreeList;
		_FreeList = obj;
	}

(4)性能对比

struct TreeNode
{
	int _val;
	TreeNode* _left;
	TreeNode* _right;

	TreeNode()
		:_val(0)
		, _left(nullptr)
		, _right(nullptr)
	{}
};


void TestObjectPool()
{
	// 申请释放的轮次
	const size_t Rounds = 10;
	// 每轮申请释放多少次
	const size_t N = 100000;
	std::vector<TreeNode*> v1;
	v1.reserve(N);
	//普通的new
	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();

	//内存池的new
	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;
}

我们来看看,测试结果;


三、TCMALLOC

(1)TCMALLOC介绍

TCMalloc 是 Google 自定义的 c 的 malloc () 和 c + + 操作符 new 的实现,用于 c 和 c + + 代码中的内存分配。

TCMalloc 将 c 的 malloc () 和 c + + 操作符 new 的内部实现替换为 TCMalloc 的实现,开发者只需编译链接 TCMalloc 的静态库或动态库即可,无需改动任何与内存分配有关的代码。

TCMalloc 通常被用于提高内存分配的性能,实现了高效的内存管理。


四、申请流程

(1)ThreadCache

①ThreadCache设计与声明:

1. 当内存申请size<=256KB时在thread cache中申请内存,计算size在自由链表中的位置,如果自由链表中有内存对象时,直接从FistList[i]中Pop一下对象,时间复杂度是O(1),且没有锁竞争。
2. 当FreeList[i]中没有对象时,则批量从central cache中获取一定数量的对象,插入到自由链
表并返回一个对象。

FreeList;  

内存对齐:

对齐规则:

 映射规则:

上面两个函数都是 各个文件用到的。 因此将他们放在comm.h里,其次因为反复用到频繁调用缘故,这里把它们设置为 内联+静态函数(不需要对象调用)。

由此,我们终于有足够的铺垫,设计ThreadCache;

如果不知道TLS的可以去看看下面的文章,或许对你有帮助。

TLS静态https://blog.csdn.net/evilswords/article/details/8191230TLS动态https://blog.csdn.net/yusiguyuan/article/details/22938671

其作用就是,让在ThreadCache内申请内存时,是无锁的状态。

class ThreadCache
{
public:
	void* Allocate(size_t size);

	void Delallocate(void* ptr,size_t size);

	void* FetchFromCentralCache(size_t index, size_t size);
private:
	FreeList _freelist[NFREELIST];
};
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;
//这里使用的静态版的
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

②ConcurrentAlloc

我们不会直接暴露ThreadCache给外面。因此我们在调用tcmalloc的时候 会进行一层封装。

void* ConcurrentAlloc(size_t size)
{
	if (pTLSThreadCache == nullptr)
	{
		pTLSThreadCache = new ThreadCache; //创建ThreadCache
	}

	return pTLSThreadCache->Allocate(size); //申请内存
}

//我们在使用free 的时候 都没有带size
//这里我们就先带size 后面会有方法解决这个问题的!
void ConcurrentDelAlloc(void* ptr,size_t size) 
{
	assert(pTLSThreadCache);
	pTLSThreadCache->Delallocate(ptr, size); 
}

③ThreadCache实现

void* ThreadCache::Allocate(size_t size)
{
	assert(size < MAX_BYTES);
	size_t alignSize = SizeClass::RoundUP(size);
	size_t index = SizeClass::Index(size);

	if (!_freelist[index].Empty())
	{
		return _freelist[index].Pop();
	}
		return FetchFromCentralCache(index, alignSize);
}

void ThreadCache::Delallocate(void* ptr, size_t size)
{
	assert(ptr);
	size_t index = SizeClass::Index(size);
	_freelist[index].Push(ptr);
}

(2)CentralCache

①CentralCache的设计与声明

1.当thread cache中没有内存时,就会 "批量" 向central cache申请一些内存对象,central cache也有一个哈希映射的freelist(与ThreadCache哈希键值一一对应),freelist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的。

2.central cache中没有非空的span时,则将空的span链在一起,向page cache申请一个span
对象,span对象中是一些以页为单位的内存,切成需要的内存大小,并链接起来,挂到span中。

Span;

 SpanList:

 

class SpanList
{
public:
	SpanList()
	{
		_head = new Span;
		_head->_next = _head;
		_head->_prev = _head;
	}

	Span* Begin()
	{
		return _head->_next;
	}

	Span* End()
	{
		return _head;
	}

	void Insert(Span* pos,Span* newSpan)
	{
		assert(pos);
		assert(newSpan);
		Span* prev = pos->_prev;

		prev->_next = newSpan;
		newSpan->_prev = prev;

		newSpan->_next = pos;
		pos->_prev = newSpan;
	}

	void Erase(Span* pos)
	{
		assert(pos);
		assert(pos != _head);
	
		Span* prev = pos->_prev;
		Span* next = pos->_next;

		prev->_next = next;
		next->_prev = prev;
	}
private:
	Span* _head;
public:
	std::mutex _mtx;
};

单例模式:

保证CentralCache 全局只有唯一确定的一个。包括之后的PageCache 也是采用单例模式。

封掉 该类的构造和拷贝构造! 并提供一个static 成员函数 调用全局唯一静态变量

class CentralCache
{
public:
	static CentralCache* GetInStance()
	{
		return &_Inst;
	}

	Span* GetOneSpan(SpanList& _list,size_t size);

	size_t FetchFromRange(void*& start,void*& end,size_t batchNum,size_t size);

private:
	SpanList _spanlist[NFREELIST];
	static CentralCache _Inst;

	CentralCache(){}
	CentralCache(const CentralCache&) = delete;
};

②CentralCache实现

ThreadCache 与 CentralCache的连通器(FetchFromCentralCache):

理解慢增长:

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
	size_t batchNum = (std::min)(SizeClass::NumMoveSize(size),_freelist[index].MaxSize());
	if (batchNum == _freelist[index].MaxSize())
	{
		_freelist[index].MaxSize() += 1;
	}

	void* start = nullptr;
	void* end = nullptr;	
	size_t acltualNum = CentralCache::GetInStance()->FetchFromRange(start, end, batchNum, size);
	assert(acltualNum > 0);
	if (acltualNum == 1)
	{
		assert(start == end);
	}
	else
	{
		_freelist[index].PushRange(NextObj(start), end, acltualNum - 1);
	}

	return start;
}

FetchRangeObj:

size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanlist[index]._mtx.lock();
	Span* span = GetOneSpan(_spanlist[index],size);
	assert(span);
	assert(span->_freelist);

	start = span->_freelist;
	end = start;
	size_t i = 0;
	size_t actulNum = 1;
	while (i < batchNum - 1 && NextObj(end))
	{
		end = NextObj(end);
		i++;
		actulNum++;
	}
	span->_freelist = NextObj(end);
	NextObj(end) = nullptr;
	_spanlist[index]._mtx.unlock();
	
	return actulNum;
}

GetOneSpan;

Span* CentralCache::GetOneSpan(SpanList& _list, size_t size)
{
	Span* it = _list.Begin();
	while (it != _list.End())
	{
		if (it->_freelist)
		{
			return it;
		}
		it = it->_next;
	}
    
    //为什么这里需要解锁? 因为在外面已经有一个桶锁了
    //此时这个线程走到这一步 已经不在CentralCache 而是要访问PageCache
    //而PageCache是需要加大锁的(之后会说)。 
    //否则如果不释放锁 那么其他要在这个位置归还span的线程 也会处在阻塞中
	_list._mtx.unlock();

	//...这里之后又是牵涉到PageCache了 那也之后再讲
}

(3)PageCache 

①PageCache的设计与声明

当central cache向page cache申请内存时,page cache先检查对应位置有没有span,如果
没有则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4page,4page后
面没有挂span,则向后面寻找更大的span,假设在10page位置找到一个span,则将10page span分裂为一个4page span和一个6page span。

         PageCache遵循的原则就是 大切小!

 单例模式:


class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_Inst;
	}

	void* NewSpan(size_t k);
    //在PageCache下 也需要有锁
	std::mutex _pagemtx;
private:
	SpanList _spanlist[NPAGES];
	static PageCache _Inst;
	PageCache() {}
	PageCache(const PageCache&) = delete;
};

②PageCache实现

字节转换为页:

 GetOneSpan;

Span* CentralCache::GetOneSpan(SpanList& _list, size_t size)
{
	Span* it = _list.Begin();
	while (it != _list.End())
	{
		if (it->_freelist)
		{
			return it;
		}
		it = it->_next;
	}

	_list._mtx.unlock();
	PageCache::GetInstance()->_pagemtx.lock();
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	PageCache::GetInstance()->_pagemtx.unlock();

	char* start = (char*)(span->_pageId <<PAGE_SHIFT);
	size_t bytes = (span->n << PAGE_SHIFT);
	char* end = start + bytes;

	span->_freelist = start;
	start += size;
	void* tail = span->_freelist;

	while (start < end)
	{
		NextObj(tail) = start;
		//tail = start;
		tail = NextObj(tail);
		start += size;
	}
	NextObj(tail) = nullptr;

    //访问该SpanList 加锁
	_list._mtx.lock();
	_list.PushFront(span);
	return span;
}

NewSpan;

Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0);
	if (!_spanlist[k].Empty())
	{
		return _spanlist[k].PopFront();
	}

	for (size_t i = k +1;k < NPAGES;++i)
	{
		if (!_spanlist[i].Empty())
		{
			Span* nSpan = _spanlist[i].PopFront();
			Span* kSpan = new Span;

			kSpan->_pageId = nSpan->_pageId;
			kSpan->n = k;

			nSpan->_pageId += k;
			nSpan->n -= k;
			
			_spanlist[nSpan->n].PushFront(nSpan);
		}
	}

	Span* bigSpan = new Span;
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_pageId = ((PAGE_ID)ptr >> PAGE_SHIFT);
	bigSpan->n = NPAGES - 1;

	_spanlist[bigSpan->n].PushFront(bigSpan);
	return NewSpan(k);
}


五、释放流程

ConcurrentFree:

当然,我们在使用free、delete的时候 ,仅仅只需要指向这块地方的指针即可。那这个问题会放在后面解决。 

ThreadCache的释放流程;

        1. 当释放内存小于64k时将内存释放回thread cache,计算size在自由链表中的位置,将对象Push到FreeList[i].
        2. 当链表的长度过长,则回收一部分内存对象到central cache。(这是为了最后解决外碎片的问题)

void ThreadCache::Delallocate(void* ptr, size_t size)
{
	assert(ptr);
	size_t index = SizeClass::Index(size);
	_freelist[index].Push(ptr);

	if (_freelist[index].MaxSize() < _freelist[index].Size())
	{
		ListTooLong(_freelist[index], _freelist[index].MaxSize());
	}
}

void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;
	list.PopRange(start, end, size);

	//...向CentralCache这一层归还span
}

CentralCache释放流程;

当thread_cache过长或者线程销毁,则会将内存释放回central cache中的,释放回来时--
use_count。当use_count减到0时则表示所有对象都回到了span,则将span释放回page
cache,page cache中会对前后相邻的空闲页进行合并。

        CentralCache是将一个完整的Span切割为小份。每次拿一块内存出去,可以用_useCount计数;

 

void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanlist[index]._mtx.lock();
	while (start)
	{
		void* next = NextObj(start);
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		NextObj(start) = span->_freelist;
		span->_freelist = start;
		span->_UseCount--;

		if (span->_UseCount == 0)
		{
			_spanlist[index].Erase(span);
			span->_freelist = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;
		
			_spanlist[index]._mtx.unlock();
			//返还给下层PageCahce
		}
		start = next;
	}
	_spanlist[index]._mtx.unlock();
}

 PageCache释放流程;

如果central cache释放回一个span,则依次寻找span的前后page id的span,看是否可以
合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span,减少内存
碎片。

void PageCache::ReleaseSpantoPageCache(Span* span)
{
	assert(span);
	while (1)
	{
		PAGE_ID prevID = span->_pageId - 1;
		auto ret = _idMapSpan.find(prevID);
		if (ret == _idMapSpan.end())
		{
			break;
		}

		Span* prevSpan = ret->second;
		if (prevSpan->n + span->n> NPAGES)
		{
			break;
		}

		if (prevSpan->_IsUse == true)
		{
			break;
		}

		span->_pageId = prevSpan->_pageId;
		span->n += prevSpan->n;

		_spanlist[prevSpan->n].Erase(prevSpan);
		delete prevSpan;
	}

	while (1)
	{
		PAGE_ID nextId = span->_pageId + span->n;
		auto ret = _idMapSpan.find(nextId);
	
		if (ret == _idMapSpan.end())
		{
			break;
		}

		Span* nextSpan = ret->second;
		if (nextSpan->_IsUse == true) break;

		if (nextSpan->n + span->n > NPAGES) break;
	
		span->n += nextSpan->n;

		_spanlist[nextSpan->n].Erase(nextSpan);
		delete nextSpan;
	}

	_spanlist[span->n].PushFront(span);
	span->_IsUse = false;
	_idMapSpan[span->_pageId] = span;
	_idMapSpan[span->_pageId + span->n - 1] = span;
}

但是别忘了 _idMapSpan在newSpan 的时候 就应当建立映射;

解决ConCurrentFree传size的问题;

解决大块内存申请的问题:

        如果大小 <= 256 KB 那么这个内存仍然起着作用!

        但是如果 > 256KB 呢? 解决这个大块内存问题该怎么做?

申请: 

释放:

解决delete、new问题;

       在span 的时候 多数用到new delete 但是我们并没有很好地进行分离。

在最早的时候,我们写的定长内存池此时就起了作用了。

 在用到new delete的地方替换即可

基本的释放+申请的流程就走完了。第一要做的 肯定是看有bug没有?


六、测试与性能瓶颈

(1)调试

 

 线程安全问题:

在访问_idMapSpan的时候 也存在线程安全问题。 因为STL不保证线程安全。当你去读取的时候,也有看其他线程在翻转。因此 需要函数内同样需要加锁。

为什么PageCache + 大锁 CentralCache+桶锁?

 因为PageCache牵涉到 前后页合并的问题。+桶锁不能避免线程安全。 而CentralCache只是拿到index 去访问这个桶的span。其他线程去访问其他桶 是完全不背离的。

 

(2)性能瓶颈

 ????什么牛马! 自己写的tcmalloc还不如 malloc自己。

我们可以来看看 性能受阻在哪里?

 

(3)基数树

可以看得出来,锁竞争对于性能的消耗是很大的。

tcmalloc中 采用一种基数树的方式。从而避免锁带来的性能损耗

概述:

        在计算机科学中,基数树,或称压缩前缀树,是一种更节省空间的Trie(前缀树)。对于基数树的每个节点,如果该节点是确定的子树的话,就和父节点合并。基数树可用来构建关联数组。 用于IP 路由。 信息检索中用于文本文档的倒排索引。

  

我们再来看看替换之后的效果吧

 

性能方面提升了很多了。


结语: 

        高并发内存池对数据结构、线程控制、设计模式有一定的要求。并且这种内存级别的项目,一旦出bug。找bug调试起来也很高。

        对我自己而言,仅仅只是模仿了TCmalloc中的一些精华,还需要更加精炼自己对这个项目的理解。

ThreadCache:


class ThreadCache
{
public:
	void* Allocate(size_t size);

	void Delallocate(void* ptr,size_t size);

	void* FetchFromCentralCache(size_t index, size_t size);

	void ListTooLong(FreeList& list, size_t size);
private:
	FreeList _freelist[NFREELIST];
};
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

void* ThreadCache::Allocate(size_t size)
{
	assert(size < MAX_BYTES);
	size_t alignSize = SizeClass::RoundUP(size);
	size_t index = SizeClass::Index(size);

	if (!_freelist[index].Empty())
	{
		return _freelist[index].Pop();
	}
		return FetchFromCentralCache(index, alignSize);
}

void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{
	size_t batchNum = (std::min)(SizeClass::NumMoveSize(size),_freelist[index].MaxSize());
	if (batchNum == _freelist[index].MaxSize())
	{
		_freelist[index].MaxSize() += 1;
	}

	void* start = nullptr;
	void* end = nullptr;	
	size_t acltualNum = CentralCache::GetInStance()->FetchRangeObj(start, end, batchNum, size);
	assert(acltualNum > 0);
	if (acltualNum == 1)
	{
		assert(start == end);
	}
	else
	{
		_freelist[index].PushRange(NextObj(start), end, acltualNum - 1);
	}

	return start;
}

void ThreadCache::Delallocate(void* ptr, size_t size)
{
	assert(ptr);
	size_t index = SizeClass::Index(size);
	_freelist[index].Push(ptr);

	if (_freelist[index].MaxSize() < _freelist[index].Size())
	{
		ListTooLong(_freelist[index],size);
	}
}

void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;
	list.PopRange(start, end, list.MaxSize());

	//...向CentralCache这一层归还span
	CentralCache::GetInStance()->ReleaseListToSpans(start, size);
}

CentralCache:
 

class CentralCache
{
public:
	static CentralCache* GetInStance()
	{
		return &_Inst;
	}

	Span* GetOneSpan(SpanList& _list,size_t size);

	size_t FetchRangeObj(void*& start,void*& end,size_t batchNum,size_t size);

	void ReleaseListToSpans(void* start, size_t n);

private:
	SpanList _spanlist[NFREELIST];
	static CentralCache _Inst;

	CentralCache(){}
	CentralCache(const CentralCache&) = delete;
};

CentralCache CentralCache::_Inst;

Span* CentralCache::GetOneSpan(SpanList& _list, size_t size)
{
	Span* it = _list.Begin();
	while (it != _list.End())
	{
		if (it->_freelist)
		{
			return it;
		}
		it = it->_next;
	}
	_list._mtx.unlock();

	PageCache::GetInstance()->_pagemtx.lock();
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	span->ObjectSize = size;
	span->_IsUse = true;
	PageCache::GetInstance()->_pagemtx.unlock();

	char* start = (char*)(span->_pageId <<PAGE_SHIFT);
	size_t bytes = (span->n << PAGE_SHIFT);
	char* end = start + bytes;

	span->_freelist = start;
	start += size;
	void* tail = span->_freelist;

	while (start < end)
	{
		NextObj(tail) = start;	
		//tail = start;
		tail = NextObj(tail);
		start += size;
	}
	NextObj(tail) = nullptr;

	_list._mtx.lock();
	_list.PushFront(span);
	return span;
}

size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanlist[index]._mtx.lock();
	Span* span = GetOneSpan(_spanlist[index],size);
	assert(span);
	assert(span->_freelist);

	start = span->_freelist;
	end = start;
	size_t i = 0;
	size_t actulNum = 1;
	while (i < batchNum - 1 && NextObj(end))
	{
		end = NextObj(end);
		i++;
		actulNum++;
	}
	span->_freelist = NextObj(end);
	NextObj(end) = nullptr;
	_spanlist[index]._mtx.unlock();
	
	return actulNum;
}


void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanlist[index]._mtx.lock();
	while (start)
	{
		void* next = NextObj(start);
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		NextObj(start) = span->_freelist;
		span->_freelist = start;
		span->_UseCount--;

		if (span->_UseCount == 0)
		{
			_spanlist[index].Erase(span);
			span->_freelist = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;
		
			_spanlist[index]._mtx.unlock();
			//返还给下层PageCahce
			PageCache::GetInstance()->_pagemtx.lock();
			PageCache::GetInstance()->ReleaseSpantoPageCache(span);
			PageCache::GetInstance()->_pagemtx.unlock();

		}
		start = next;
	}
	_spanlist[index]._mtx.unlock();
}

PageCache:

class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_Inst;
	}

	Span* NewSpan(size_t k);

	Span* MapObjectToSpan(void* obj);

	void ReleaseSpantoPageCache(Span* span);

	std::mutex _pagemtx;
private:
	SpanList _spanlist[NPAGES];
	static PageCache _Inst;
	ObjectPool<Span> _spanPool;
	TCMalloc_PageMap1<32 - PAGE_SHIFT> _idMapSpan;

	//std::unordered_map<PAGE_ID, Span*> _idMapSpan;

	PageCache() {}
	PageCache(const PageCache&) = delete;
};

PageCache PageCache::_Inst; 

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->_pageId = ((PAGE_ID)ptr >> PAGE_SHIFT);
		span->n = k;
		//_idMapSpan[span->_pageId] = span;
		_idMapSpan.set(span->_pageId,span);

		return span;
	}
	else
	{
		if (!_spanlist[k].Empty())
		{
			Span* kSpan = _spanlist[k].PopFront();
			for (PAGE_ID i = 0;i < kSpan->n;++i)
			{
				/*_idMapSpan[kSpan->_pageId + i] = kSpan;*/
				_idMapSpan.set(kSpan->_pageId+i, kSpan);
			}

			return kSpan;
		}

		for (size_t i = k + 1; i < NPAGES;++i)
		{
			if (!_spanlist[i].Empty())
			{
				Span* nSpan = _spanlist[i].PopFront();
				Span* kSpan = _spanPool.New();
				//Span* kSpan = new Span;

				kSpan->_pageId = nSpan->_pageId;
				kSpan->n = k;

				nSpan->_pageId += k;
				nSpan->n -= k;

				_spanlist[nSpan->n].PushFront(nSpan);
	/*			_idMapSpan[nSpan->_pageId] = nSpan;
				_idMapSpan[nSpan->_pageId + nSpan->n - 1] = nSpan;*/
				_idMapSpan.set(nSpan->_pageId, nSpan);
				_idMapSpan.set(nSpan->_pageId + nSpan->n - 1, nSpan);

				for (PAGE_ID i = 0;i < kSpan->n;++i)
				{
					//_idMapSpan[kSpan->_pageId + i] = kSpan;
					_idMapSpan.set(kSpan->_pageId + i, kSpan);
				}
				return kSpan;
			}
		}

		//Span* bigSpan = new Span;
		Span* bigSpan = _spanPool.New();
		void* ptr = SystemAlloc(NPAGES - 1);
		bigSpan->_pageId = ((PAGE_ID)ptr >> PAGE_SHIFT);
		bigSpan->n = NPAGES - 1;
		_spanlist[bigSpan->n].PushFront(bigSpan);

		return NewSpan(k);
	}
}

Span* PageCache::MapObjectToSpan(void* obj)
{
	PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT;
	auto span = (Span*)_idMapSpan.get(id);
	assert(span);
	return span;

	//std::unique_lock<std::mutex>lock(_pagemtx);
	//auto ret = _idMapSpan.find(id);
	//if (ret != _idMapSpan.end())
	//{
	//	return ret->second;
	//}
	//else
	//{
	//	assert(false);
	//	return nullptr;
	//}
}

void PageCache::ReleaseSpantoPageCache(Span* span)
{
	assert(span);
	if (span->n > NPAGES - 1)
	{
		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);
		return;
	}

	while (1)
	{
		PAGE_ID prevID = span->_pageId - 1;
		Span* prevSpan = (Span*)_idMapSpan.get(prevID);
	/*	auto ret = _idMapSpan.find(prevID);*/
		if (prevSpan == nullptr)
		{
			break;
		}

		if (prevSpan->n + span->n> NPAGES)
		{
			break;
		}

		if (prevSpan->_IsUse == true)
		{
			break;
		}

		span->_pageId = prevSpan->_pageId;
		span->n += prevSpan->n;

		_spanlist[prevSpan->n].Erase(prevSpan);
		//delete prevSpan;
		_spanPool.Delete(prevSpan);
	}

	while (1)
	{
		PAGE_ID nextId = span->_pageId + span->n;
		Span* nextSpan = (Span*)_idMapSpan.get(nextId);
		/*auto ret = _idMapSpan.find(nextId);*/
	/*	if (ret == _idMapSpan.end())
		{
			break;
		}*/

		if (nextSpan == nullptr) break;
		if (nextSpan->_IsUse == true) break;

		if (nextSpan->n + span->n > NPAGES) break;
	
		span->n += nextSpan->n;

		_spanlist[nextSpan->n].Erase(nextSpan);
		//delete nextSpan;
		_spanPool.Delete(nextSpan);
	}

	_spanlist[span->n].PushFront(span);
	span->_IsUse = false;
	//_idMapSpan[span->_pageId] = span;
	//_idMapSpan[span->_pageId + span->n - 1] = span;
	_idMapSpan.set(span->_pageId, span);
	_idMapSpan.set(span->_pageId+span->n - 1, span);
}

本篇就到此结束了

感谢你的阅读! 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值