【項目】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內存池

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