高并发内存池

news2024/11/17 10:57:31

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


一、技术介绍

(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);
}

本篇就到此结束了

感谢你的阅读! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/96599.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【单调栈】接雨水

文章目录双指针动态规划单调栈双指针 每一列雨水的高度&#xff0c;取决于该列 min(左侧最高的柱子高度&#xff0c;右侧最高的柱子高度) - 当前柱子高度 class Solution { public:int trap(vector<int>& height) {int n height.size();int ans 0;for(int i 1; …

ZY_BMP280大气压模块的使用和程序

在搞设计时&#xff0c;用到了大气压模块BMP280&#xff0c;这玩意也不难&#xff0c;主要是淘宝店老板特别der一问三不知&#xff0c;真服了&#xff0c;而且资料里面没有给例程&#xff0c;只有51的例程&#xff0c;利用IIC通信&#xff0c;而且给的例程里面&#xff0c;乱七…

数据库实验五:数据库设计实验

实验五 数据库设计实验 1.实验目的 ​ 掌握数据库设计基本方法及数据库设计工具。 2.实验内容和要求 ​ 掌握数据库设计基本步骤&#xff0c;包括数据库概念结构设计、逻辑结构设计&#xff0c;物理结构设计&#xff0c;数据库模式SQL语句生成。能够使用数据库设计工具进行…

ctf笔记:编码

常见编码 博客链接&#xff1a;https://www.blog.23day.site/articles/76 计算机中的数据都是按字节存储。一个字节(Byte)由8个二进制位组成(bit)。&#xff08;组成范围是0~255(28))一个字节一共可以用来表示256种不同的状态&#xff0c;每一个状态对应一个符号&#xff0c;就…

推荐系统学习笔记-隐语义模型

由来 该算法最早在文本挖掘领域被提出&#xff0c;用于找到文本的隐含语义。 核心思想是通过隐含特征(latent factor) 联系用户兴趣和物品。 参数 f&#xff1a;隐向量维度&#xff0c;决定隐向量表达能力强弱 n&#xff1a;用户数 m&#xff1a;物品数 求解方法&#xff1a;…

ADI Blackfin DSP处理器-BF533的开发详解57:DSP控制ADV7180采集图像到LCD显示(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 代码实现功能 代码实现了采集一帧 720625 尺寸的 P 制 CVBS 信号源&#xff0c;以 YUYV422 的数据格式保存&#xff0c;通过 MDMA 将奇偶场数据交…

【轻松掌握C语言】文件操作

目录 一、为什么使用文件&#xff1f; 二、什么是文件&#xff1f; 1、程序文件 2、数据文件 3、文件名 三、文件操作 1、文件指针 2、文件打开与关闭 . 3、文件的顺序读写 4、文件的随机读写 5、文本文件和二进制文件 6、文件读取结束判定 四、文件缓冲区 一、…

C++ Reference: Standard C++ Library reference: Containers: map: map: rbegin

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/rbegin/ 公有成员函数 <map> std::map::rbegin C98 reverse_iterator rbegin(); const_reverse_iterator rbegin() const; C11 reverse_iterator rbegin() noexcept; const_reverse_iterator rbegin(…

这个大力神杯,梅西已足足等了16年,AI预测:阿根廷冠军

潘帕斯雄鹰和高卢雄鸡的决战&#xff0c;在三十多小时后即将上演。AI预测&#xff1a;胜率接近&#xff0c;阿根廷略高。 12月18日&#xff0c;卡塔尔世界杯总决赛将正式开战。 由卫冕军法国对上寻求队史第三冠的阿根廷&#xff0c;同时也是两位顶尖球星兼巴黎圣日耳曼队友梅…

LeetCode | 二叉树高频面试算法题汇总【速来】

小伙子&#xff0c;来给我✍棵树【LeetCode】144.二叉树的前序遍历C版本C语言版本&#xff08;递归算法展开图&#xff09;【LeetCode】94.二叉树的中序遍历C版本C语言版本【LeetCode】145.二叉树的后序遍历C版本C语言版本【LeetCode】102.二叉树的层序遍历DSF——深度优先搜索…

Linux 之centos7:一、Linux安装

Linux 之centos7 1.Linux简介 ​ Linux内核最初只是由芬兰人李纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在赫尔辛基大学上学时出于个人爱好而编写的。 ​ Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和UNIX的多用户、多任务、支持多…

C#,基于视频的目标识别算法(Moving Object Detection)的原理、挑战及其应用

本文概述了基于监控视频之连续帧信息的各种目标识别算法及其存在的问题与挑战&#xff0c;结合实际应用开发的工作&#xff0c;文中给出了实验性基于帧差算法和改进型背景算法的非人工智能目标识别算法的实际效果。 目标识别算法一直并将持续成为人工智能研究与应用的重点&…

交换机设备上的G口、F口、E口、S口区别是什么?一台交换机有哪些接口呢?每个接口都有哪些作用?

交换机设备上的G口、F口、E口、S口区别是什么? 一台交换机有哪些接口呢?每个接口都有哪些作用? 交换机的主要功能包括:学习功能、转发过滤和消除回路。 学习功能:以太网交换机知道连接到每个端口的设备的MAC地址,将该地址与相应的端口进行映射,并存储在交换机缓存的MA…

jsp+ssm计算机毕业设计大学生互助系统【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

点云 3D 目标检测 - VoxelNet(CVPR 2018)

点云 3D 目标检测 - VoxelNet&#xff08;CVPR 2018&#xff09;摘要1. 引言1.1 相关工作1.2 贡献2. VoxelNet2.1 VoxelNet架构2.1.1 特征学习网络2.1.2 卷积中层2.1.3 区域提案网络2.2 损失函数2.3 高效实施3. 训练详情3.1 网络详细信息3.2 数据增强4. 实验4.1 KITTI验证集评估…

【算法】动态规划 ⑧ ( 动态规划特点 )

文章目录一、动态规划特点1、求解类型2、方向性3、动态规划状态选择4、动态规划方程设计一、动态规划特点 1、求解类型 求解类型 : 动态规划 必须是求 最值 , 可行性 , 方案数 , 三者之一 , 如果求其它内容 , 则不能使用动态规划算法 ; 求最值 : 最大值 , 最小值 等 ; 大规模问…

[附源码]Node.js计算机毕业设计工资管理系统PPTExpress

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

加入CSDN的第一百天,也是学C的第一百天

加入CSDN的一百天 1.学习总结 2.个人感悟 1.学习总结 学习c语言已经有100天&#xff0c;从一个初出茅庐的无知青年&#xff0c;敲出第一个hello world 都激动的不行&#xff0c;到现在&#xff1a; 常见的数据类型&#xff0c; 变量的命名方式&#xff0c; 变量的分类 到变…

[2022-12-17]神经网络与深度学习 hw9 - bptt

contentshw9 - Back Propagation Through Timetask1题目内容题目思路题目解答题目总结task2题目内容题目思路题目解答题目总结hw9 - Back Propagation Through Time task1 题目内容 推导RNN反向传播算法BPTT。 题目思路题目解答 首先我们要清楚RNN进行前向传播的过程&…

0. Canal 的安装和使用

我看过一场风景&#xff0c;后来我才知道&#xff0c;那是我人生中最美的一段时光。 我爱的人&#xff0c;爱我的人&#xff0c;都能度过这场新型感冒&#xff0c;那该多好。 Canal 的官网: https://github.com/alibaba/canal Canal 能干什么 为什么出现 Canal Canal 是阿里…