紧跟
高并发内存池申请内存联调接下来就是高并发内存池的内存释放过程
ThreadCache释放内存,将一部分多余的空间节点链表给CentralCache
如果ThreadCache中的FreeList中空间节点超过要申请的长度,回收内存。 这些回收的内存节点给到Central Cache,Central Cache将这些空间节点合并成Span挂到Span哈希桶的对应位置。
#pragma once
#include"Common.h"
#include"CentralCache.h"
class ThreadCache {
private:
FreeList _FreeList[NUMLIST];
public:
void* ApplySpace(size_t size) {
assert(size <= MAX_BYTE);
size_t AligSize = SizeClass::RoundUp(size);
size_t index = SizeClass::Index(AligSize);
if (!_FreeList[index].Empty()) {
return _FreeList[index].Pop();
}
else {
return RequestFromCentralCache(index, AligSize);
}
}
void ReleaseSpace(void* ptr, size_t size) {
assert(size <= MAX_BYTE && ptr != nullptr);
size_t index = SizeClass::Index(size);
_FreeList[index].Push(ptr);
if (_FreeList[index].GetMaxSize() < _FreeList[index].GetSize()) {
RetrunCentralCache(_FreeList[index], size);
}
}
void RetrunCentralCache(FreeList& List, size_t size) {
void* start = nullptr; void* end = nullptr;
List.PopList(start, end, List.GetMaxSize());
CentralCache::GetCentralCache()->MergeSpan(start, size);
}
void* RequestFromCentralCache(size_t index, size_t size) {
size_t Num = min(SizeClass::ForMemory(size),_FreeList[index].GetMaxSize());
if (Num == _FreeList[index].GetMaxSize()) {
_FreeList[index].GetMaxSize() += 2;
}
void* begin = nullptr; void* end = nullptr;
size_t actualNum = CentralCache::GetCentralCache()->GiveThreadCache(begin, end, Num, size);
assert(actualNum >= 1);
if (actualNum == 1) {
return begin;
}
else {
_FreeList[index].PushList(NextObj(begin), end, actualNum - 1);
return begin;
}
}
};
static __declspec(thread) ThreadCache* tls_threadcache = nullptr;
自由链表类需要添加的成员函数
- 获取自由链表的节点数
- 自由链表弹出len个节点给CenralCache
class FreeList {
public:
FreeList() :_freeList(nullptr), MaxSize(1), size(0) {}
void Push(void* obj) {
assert(obj);
NextObj(obj) = _freeList;
_freeList = obj;
++size;
}
void* Pop() {
assert(_freeList);
void* obj = _freeList;
_freeList = NextObj(obj);
--size;
return obj;
}
void PushList(void* begin, void* end, size_t len) {
NextObj(end) = _freeList;
_freeList = begin;
size += len;
}
void PopList(void*& begin, void*& end, size_t len) {
assert(len >= size);
begin = _freeList; end = begin;
for (size_t i = 0; i < len - 1; i++) {
end = NextObj(end);
}
_freeList = NextObj(end);
NextObj(end) = nullptr;
size -= len;
}
bool Empty() { return _freeList == nullptr; }
size_t GetSize() { return size; }
size_t& GetMaxSize() { return MaxSize; }
private:
void* _freeList;
size_t MaxSize;
size_t size;
};
CentralCache将从ThreadCache获取的空间节点合并为Span(MergeSpan)
需要注意的是,归还内存的时候需要修改CentralCache的Span链表,需要加桶锁。
此外,线程归还内存时这块内存属于Span链表中的那个Span不确定。
如果直到一块空间的页号,这块空间的地址为页号×页大小(这块内存任意位置÷页大小都是页号)用来判断这块内存属于那一页 eg:
void TestPageId() {
PAGE_ID id = 2020;
PAGE_ID id2 = 2021;
char* start = (char*)(id << PAGESIZE);
char* end = (char*)(id2 << PAGESIZE);
cout << (void*)start << " " << (void*)end << endl;
while (start <= end) {
cout << (void*)start << " PAGEID:" << ((PAGE_ID)start >> PAGESIZE) << endl;
start += 8;
}
}
int main()
{
TestPageId();
return 0;
}
综上,知道一块内存的地址,可以确定这块内存的页号。为了方便找到那个Span,这里选择添加页号与Span链表的映射std::unordered_map<PAGE_ID, Span*>IdSpanMap; 这个映射放到PageCache上
CentralCache
#pragma once
#include"Common.h"
class CentralCache {
private:
SpanList _SpanList[NUMLIST];
CentralCache(const CentralCache&) = delete;
static CentralCache _sInst;
public:
CentralCache() {}
static CentralCache* GetCentralCache() {
return &_sInst;
}
Span* GetSpan(SpanList& List, size_t size);
size_t GiveThreadCache(void*& start, void*& end, size_t Num, size_t Size);
void MergeSpan(void* start, size_t size);
};
#include"CentralCache.h"
#include"PageCache.h"
CentralCache CentralCache::_sInst;
size_t CentralCache::GiveThreadCache(void*& start, void*& end, size_t Num, size_t size) {
size_t index = SizeClass::Index(size);
_SpanList[index]._mtx.lock();
Span* span = GetSpan(_SpanList[index], size);
assert(span != nullptr && span->FreeList != nullptr);
start = span->FreeList; end = start;
size_t actualNum = 1;
for (int i = 0; i < Num - 1; i++) {
if (NextObj(end) == nullptr) break;
actualNum += 1;
end = NextObj(end);
}
span->FreeList = NextObj(end);
NextObj(end) = nullptr;
span->use_count += actualNum;
_SpanList[index]._mtx.unlock();
return actualNum;
}
Span* CentralCache::GetSpan(SpanList& List, size_t size) {
Span* it = List.begin();
while (it != List.end()) {
if (it->FreeList != nullptr) {
return it;
}
else {
it = it->_next;
}
}
List._mtx.unlock();
PageCache::GetInst()->_PageMtx.lock();
Span* span=PageCache::GetInst()->NewSpan(SizeClass::NumForPage(size));
span->IsUse = true;
PageCache::GetInst()->_PageMtx.unlock();
char* start = (char*)((span->_PageID) << PAGESIZE);
size_t ByteSize = (span->_Num) << PAGESIZE;
char* end = start + ByteSize;
span->FreeList = start;
start += size;
void* tail = span->FreeList;
while (start < end) {
NextObj(tail) = start;
tail = NextObj(tail);
start += size;
}
List._mtx.lock();
List.Insert(List.begin(), span);
return span;
}
void CentralCache::MergeSpan(void* start, size_t size) {
size_t index = SizeClass::Index(size);
_SpanList[index]._mtx.lock();
while (start != nullptr) {
void* next = NextObj(start);
Span* span = PageCache::GetInst()->MapObjToSpan(start);
NextObj(start) = span->FreeList;
span->FreeList = start;
span->use_count--;
if (span->use_count == 0) {
_SpanList[index].Erase(span);
span->FreeList = nullptr;
span->_next = nullptr;
span->_prev = nullptr;
_SpanList[index]._mtx.unlock();
PageCache::GetInst()->_PageMtx.lock();
PageCache::GetInst()->RetrunPageCache(span);
PageCache::GetInst()->_PageMtx.unlock();
_SpanList[index]._mtx.lock();
}
start = next;
}
_SpanList[index]._mtx.unlock();
}
PageCache添加映射
#pragma once
#include"Common.h"
class PageCache {
private:
SpanList _SpanList[NPAGE];
static PageCache _sInst;
std::unordered_map<PAGE_ID, Span*>IdSpanMap;
PageCache() {}
public:
std::mutex _PageMtx;
PageCache(const PageCache&) = delete;
Span* MapObjToSpan(void* obj);
static PageCache* GetInst() {
return &_sInst;
}
void RetrunPageCache(Span* span);
Span* NewSpan(size_t NumPage);
};
#include"PageCache.h"
PageCache PageCache::_sInst;
Span* PageCache::NewSpan(size_t NumPage) {
assert(NumPage > 0 && NumPage < NPAGE);
if (!_SpanList[NumPage].Empty()) {
return _SpanList->Pop();
}
for (size_t i = NumPage + 1; i < NPAGE; i++) {
if (!_SpanList[i].Empty()) {
Span* NumPageSpan = new Span;
Span* NSpan = _SpanList[i].Pop();
NumPageSpan->_PageID = NSpan->_PageID;
NumPageSpan->_Num = NumPage;
NSpan->_PageID += NumPage;
NSpan->_Num -= NumPage;
_SpanList[NSpan->_Num].Insert(_SpanList[NSpan->_Num].begin(), NSpan);
for (PAGE_ID i = 0; i < NumPageSpan->_Num; i++) {
IdSpanMap[NumPageSpan->_PageID + i] = NumPageSpan;
}
return NumPageSpan;
}
}
Span* BigSpan = new Span;
void* ptr = SystemAlloc(NPAGE - 1);
BigSpan->_PageID = (PAGE_ID)ptr >> PAGESIZE;
BigSpan->_Num = NPAGE - 1;
_SpanList[BigSpan->_Num].Insert(_SpanList[BigSpan->_Num].begin(), BigSpan);
return NewSpan(NumPage);
}
Span* PageCache::MapObjToSpan(void* obj) {
PAGE_ID pageId = (PAGE_ID)obj >> PAGESIZE;
std::unordered_map<PAGE_ID,Span*>::iterator ret = IdSpanMap.find(pageId);
if (ret != IdSpanMap.end()) {
return ret->second;
}
else {
assert(false);
return nullptr;
}
}
void PageCache::RetrunPageCache(Span* span) {
}
PageCache将前后页号的从CentralCache获取的Span合并成一个大Span挂起
获得CentralCache的Span后,对Span前后页进行合并,缓解内存碎片外碎片问题。
为了找前后页对应的Span还需要页号和Span的映射,这个映射在上一层已经介绍。 如果前后页在PageCache桶上就可以合并,如果还在被CentralCache使用则不能被合并。 注意:这时不能看Span中use_cout因为在对线程的情况下,可能线程1还没有改变use_cout时,线程2来将这块空间又合并到PageCache。存在线程安全问题
这时添加一个状态IsUse标记这个Span是否被使用
struct Span {
PAGE_ID _PageID;
size_t _Num;
Span* _next;
Span* _prev;
size_t use_count;
void* FreeList;
bool IsUse;
Span() :_PageID(0), _Num(0), _next(nullptr), _prev(nullptr), use_count(0), FreeList(nullptr), IsUse(false) {}
};
- 当遇到正在使用的Span时停止合并。(前后合并)
- 和前Span合并大小超过128KB则停止合并(最大桶的大小时128KB)
PageCache
#pragma once
#include"Common.h"
class PageCache {
private:
SpanList _SpanList[NPAGE];
static PageCache _sInst;
std::unordered_map<PAGE_ID, Span*>IdSpanMap;
PageCache() {}
public:
std::mutex _PageMtx;
PageCache(const PageCache&) = delete;
Span* MapObjToSpan(void* obj);
static PageCache* GetInst() {
return &_sInst;
}
void RetrunPageCache(Span* span);
Span* NewSpan(size_t NumPage);
};
#include"PageCache.h"
PageCache PageCache::_sInst;
Span* PageCache::NewSpan(size_t NumPage) {
assert(NumPage > 0 && NumPage < NPAGE);
if (!_SpanList[NumPage].Empty()) {
return _SpanList->Pop();
}
for (size_t i = NumPage + 1; i < NPAGE; i++) {
if (!_SpanList[i].Empty()) {
Span* NumPageSpan = new Span;
Span* NSpan = _SpanList[i].Pop();
NumPageSpan->_PageID = NSpan->_PageID;
NumPageSpan->_Num = NumPage;
NSpan->_PageID += NumPage;
NSpan->_Num -= NumPage;
_SpanList[NSpan->_Num].Insert(_SpanList[NSpan->_Num].begin(), NSpan);
IdSpanMap[NSpan->_PageID] = NSpan;
IdSpanMap[NSpan->_PageID + NSpan->_Num - 1] = NSpan;
for (PAGE_ID i = 0; i < NumPageSpan->_Num; i++) {
IdSpanMap[NumPageSpan->_PageID + i] = NumPageSpan;
}
return NumPageSpan;
}
}
Span* BigSpan = new Span;
void* ptr = SystemAlloc(NPAGE - 1);
BigSpan->_PageID = (PAGE_ID)ptr >> PAGESIZE;
BigSpan->_Num = NPAGE - 1;
_SpanList[BigSpan->_Num].Insert(_SpanList[BigSpan->_Num].begin(), BigSpan);
return NewSpan(NumPage);
}
Span* PageCache::MapObjToSpan(void* obj) {
PAGE_ID pageId = (PAGE_ID)obj >> PAGESIZE;
std::unordered_map<PAGE_ID,Span*>::iterator ret = IdSpanMap.find(pageId);
if (ret != IdSpanMap.end()) {
return ret->second;
}
else {
assert(false);
return nullptr;
}
}
void PageCache::RetrunPageCache(Span* span) {
while (true) {
PAGE_ID prevId = span->_PageID - 1;
std::unordered_map<PAGE_ID, Span*>::iterator ret = IdSpanMap.find(prevId);
if (ret == IdSpanMap.end()) {
break;
}
else {
if (ret->second->IsUse == true) {
break;
}
else if (ret->second->_Num + span->_Num >= NPAGE) {
break;
}
else {
span->_PageID = ret->second->_PageID;
span->_Num += ret->second->_Num;
_SpanList[ret->second->_Num].Erase(ret->second);
delete ret->second;
}
}
}
while (true) {
PAGE_ID nextId = span->_PageID + span->_Num;
std::unordered_map<PAGE_ID, Span*>::iterator ret = IdSpanMap.find(nextId);
if (ret == IdSpanMap.end()) {
break;
}
else {
Span* nextSpan = ret->second;
if (nextSpan->IsUse == true) {
break;
}
else if (nextSpan->_Num + span->_Num >= NPAGE) {
break;
}
else {
span->_Num += nextSpan->_Num;
_SpanList[nextSpan->_Num].Erase(nextSpan);
IdSpanMap.erase(nextSpan->_Num);
delete nextSpan;
}
}
}
_SpanList[span->_Num].Insert(_SpanList[span->_Num].begin(), span);
span->IsUse = false;
IdSpanMap[span->_PageID] = span;
IdSpanMap[span->_PageID + span->_Num - 1] = span;
}
多线程下测试
#include"ConcurrentAlloc.h"
void f1() {
std::vector<void*>vet;
for (int i = 0; i < 10; i++) {
void* ptr = ConcurrentAlloc(6);
vet.push_back(ptr);
}
for (auto e : vet) {
ConcurrentFree(e, 6);
}
}
void f2() {
std::vector<void*>vet;
for (int i = 0; i < 10; i++) {
void* ptr = ConcurrentAlloc(7);
vet.push_back(ptr);
}
for (auto e : vet) {
ConcurrentFree(e, 7);
}
}
void TestConcurrentAllocThread() {
std::thread t1(f1);
t1.join();
std::thread t2(f2);
t2.join();
}
int main()
{
TestConcurrentAllocThread();
return 0;
}
运行结果: 走到这里项目主体基本完成剩下的就是效率测试与优化
代码位置
Github Gitee
|