【项目】HC内存池

1. 什么是内存池?

1.1 池化技术

池是一种设计模式,将程序中需要经常使用的核心资源先申请出来,放到一个池内,由程序自己管理,提高资源的使用效率,保证本程序占有的资源数量。经常使用的池技术包括内存池、线程池和连接池等,其中内存池和线程池使用最多。

1.2 内存池

内存池(Memory Pool) 是一种动态内存分配与管理技术。 通常情况下,程序员习惯直接使用 new、delete、malloc、free 等API申请分配和释放内存,这样导致的后果是:当程序长时间运行时,由于所申请内存块的大小不定,频繁使用时会造成大量的内存碎片从而降低程序和操作系统的性能。

内存池在真正使用内存之前,先申请分配一大块内存(内存池)留作备用,当程序员申请内存时,从池中取出一块动态分配,当程序员释放内存时,将释放的内存再放入池内,再次申请池可以再取出来使用,并尽量与周边的空闲内存块合并。若内存池不够时,则自动扩大内存池,从操作系统中申请更大的内存池。

2. HC内存池(High Concurrency Memory Pool)

2.1 解决问题

  • 解决内存碎片问题;
  • 解决性能问题;
  • 并发性能。

2.2 组成

HC内存池主要由3部分组成: thread cache(线程缓存)、central cache(中心缓存)、page cache(页缓存)。

  • thread cache:线程缓存是每个线程独有的,不需要加锁的;

  • central cache:中心缓存是所有线程所共享,均衡按需调度;

  • page cache:页缓存所存储的内存是以页为单位存储及分配的,缓解内存碎片的问题。

【整体框架流程图】
在这里插入图片描述
3.3 详解

3.3.1 第一层 thread cache

  • thread cache:线程缓存是每个线程独有的,用于小于64k的内存的分配,每个线程独享一个cache,因此thread cache中申请或回收内存是不需要加锁的;

  • 若申请内存大于64k? -》 向page cache申请;

  • thread cache本质是由一个哈希映射的对象自由链表构成。

【申请内存】

  • 当内存申请size <= 64k时在thread cache中申请内存,计算size在自由链表中的位置,如果自由链表中有内存对象时,直接从FistList[i]中pop一下对象,时间复杂度是O(1),且没有锁竞争;

  • 当FreeList[i]中没有对象时,则批量从central cache中获取一定数量的对象,插入到自由链表并返回一个对象。

【释放内存】

  • 当释放内存小于64k时将内存释放回thread cache,计算size在自由链表中的位置,将对象push到FreeList[i];

  • 当链表的长度过长,则回收一部分内存对象到central cache。

class ThreadCache
{
public:
	// 申请内存和释放内存
	void* Allocte(size_t size);
	void Deallocte(void* ptr, size_t size);

	// 从中心缓存获取对象
	void* FetchFromCentralCache(size_t index);

	// 如果自由链表中对象超过一定长度就要释放给中心缓存
	void ListTooLong(FreeList& freeList, size_t num, size_t size);
private:
	FreeList _freeLists[NFREE_LIST]; 

};

// 线程TLS Thread Local Storage
_declspec (thread) static ThreadCache* pThreadCache = nullptr;

3.3.2 第二层 central cache

  • central cache:中心缓存是所有线程所共享,均衡按需调度。thread cache是按需从central cache中获取的对象,central cache周期性的回收 thread cache中的对象,避免一个线程占用了太多的内存,而造成其他线程的内存吃紧。central cache是存在竞争的,所以central cache中申请或回收内存是需要加锁的;

  • central cache本质是由一个哈希映射的span对象自由链表构成;

  • 设计成了单例模式,保证全局只有唯一的central cache;

  • span:双向、带头、循环链表,大小为一页。在这里插入图片描述

【申请内存】

  • 当thread cache中没有内存时,就会批量向central cache申请一些内存对象,central cache有一个哈希映射的freelist,freelist中挂着span,从span中取出对象给thread cache,这个过程是需要加锁的;

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

  • central cache的span中有一个use_count,分配一个对象给thread cache,就++use_count。

【释放内存】

  • 当thread cache过长或者线程销毁,则会将内存释放回central cache中的,释放回来时 --use_count;
  • 当use_count减到0时则表示所有对象都回到了span,则将span释放回page cache,page cache中会对前后相邻的空闲页进行合并。
struct Span //跨度类
{
	PAGE_ID _pageid = 0; // 页号
	PAGE_ID _pagesize = 0; // 页的数量

	FreeList _freeList;  // 对象自由链表
	size_t _objSize = 0; // 自由链表对象大小
	int _usecount = 0;   // 内存块对象使用计数

	Span* _next = nullptr; //链接span前后
	Span* _prev = nullptr;
};
class CentralCache
{
public:
	// 从中心缓存获取一定数量的对象给thread cache
	size_t FetchRangeObj(void*& start, void*& end, size_t num, size_t size); 

	// 将一定数量的对象释放到span跨度
	void ReleaseListToSpans(void* start, size_t size);

	// 从spanlist 或者 page cache获取一个span
	Span* GetOneSpan(size_t size);

	static CentralCache& GetInsatnce()
	{
		static CentralCache inst;
		return inst;
	}

private:
	//单例:保证创建出一个对象
	CentralCache() 
	{}

	CentralCache(const CentralCache&) = delete; 
	
	// 中心缓存span双向自由链表
	SpanList _spanLists[NFREE_LIST];
};

3.3.3 第三层 page cache

  • page cache:页缓存所存储的内存是以页为单位存储及分配的,central cache没有内存对象时,从page cache分配出一定数量的page,并切割成定长大小的小块内存,分配给central cache。page cache会回收central cache满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片的问题。

  • 申请内存大于128页? -》 向系统申请;

  • 设计成了单例模式,保证全局只有唯一的page cache;

  • page cache是一个以页为单位的span自由链表。在这里插入图片描述

【申请内存】

  • 当central cache向page cache申请内存时,page cache先检查对应位置有没有span,如果没有则向更大页寻找一个span,如果找到则分裂成两个;

    • 例如:申请的是4page,4page后面没有挂span,则向后面寻找更大的span,假设在10page位置找到一个span,则将10page span分裂为一个4page span和一个6page span。
  • 如果找到128 page都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式申请128page span挂在自由链表中,再重复上述中的过程。

【释放内存】

  • 如果central cache释放回一个span,则依次寻找span的前后page id的span,看是否可以合并,如果合并继续向前寻找。则可以将切小的内存合并收缩成大的span,减少内存碎片。
class PageCache
{
public:
	// 申请一个新span
	Span* _NewSpan(size_t numpage);
	Span* NewSpan(size_t numpage);

	// 释放空闲span回到Pagecache,并合并相邻的span
	void ReleaseSpanToPageCache(Span* span);

	Span* GetIdToSpan(PAGE_ID id);

	static PageCache& GetPageCacheInstance()
	{
		static PageCache inst;
		return inst;
	}

private:
	// 单例模式
	PageCache()
	{}

	PageCache(const PageCache&) = delete;

	SpanList _spanLists[MAX_PAGES];
	std::unordered_map<PAGE_ID, Span*>  _idSpanMap;

	std::mutex _mtx;
};

3.3.4 系统

  • 申请内存大于128时,找系统申请;
  • windows:VirtualAlloc;linux:brk、mmap等。
inline static void* SystemAlloc(size_t num_page) //向系统申请内存
{
#ifdef _WIN32
	void* ptr = VirtualAlloc(0, num_page*(1 << PAGE_SHIFT), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	// brk mmap等
#endif
	if (ptr == nullptr) 
		throw std::bad_alloc();

	return ptr;
}

4. 源码

https://github.com/yang1127/Project/tree/master/HC内存池

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章