1. 定长内存池配合脱离new申请空间
根据C++项目高并发内存池_定长内存池可知,使用定长内存池比直接new申请对象效率要高。
在PageCache向系统申请内存时我们使用new创建了许多Span对象来管理向系统申请的大块内存,这个Span对象是new出来的。以及在线程创建属于自己的ThreadCache时,也使用了new来申请ThreadCache对象,我们可以联系定长内存池,把这些申请过程用定长内存池替代,效率更高。
定长线程池:
#pragma once
#include"Common.h"
template<class T>
class ObjectPool {
private:
char* _memory;
void* _freeList;
size_t _overage;
public:
ObjectPool() :_memory(nullptr), _freeList(nullptr),_overage(0) {}
T* New() {
T* obj = nullptr;
if (_freeList != nullptr) {
void* next = *((void**)_freeList);
obj = (T*)_freeList;
_freeList = next;
}
else
{
if (_overage < sizeof(T)) {
_overage = 100 * 1024;
_memory = (char*)SystemAlloc(_overage);
if (_memory == nullptr) {
throw std::bad_alloc();
}
}
obj = (T*)_memory;
size_t SizeT = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
_memory += SizeT;
_overage -= SizeT;
}
new(obj)T;
return obj;
}
void Delete(T* obj) {
obj->~T();
*(void**)obj = _freeList;
_freeList = obj;
}
};
所以在PageCache中需要新定义成员定长内存池
#pragma once
#include"Common.h"
#include"ObjectPool.h"
class PageCache {
private:
SpanList _SpanList[NPAGE];
static PageCache _sInst;
std::unordered_map<PAGE_ID, Span*>IdSpanMap;
ObjectPool<Span>_SpanPool;
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);
};
在PageCache中每当申请/释放Span时用定长内存池New/Delete
线程在获得自己的Thread Cache时也使用定长内存池来申请对象
#pragma once
#include"Common.h"
#include"ThreadCache.h"
#include"PageCache.h"
#include"ObjectPool.h"
static void* ConcurrentAlloc(size_t size) {
if (size > MAX_BYTE) {
size_t AlignSize = SizeClass::RoundUp(size);
size_t K = AlignSize >> PAGESIZE;
PageCache::GetInst()->_PageMtx.lock();
Span*span=PageCache::GetInst()->NewSpan(K);
PageCache::GetInst()->_PageMtx.unlock();
void* ptr = (void*)(span->_PageID << PAGESIZE);
return ptr;
}
else {
if (tls_threadcache == nullptr) {
static ObjectPool<ThreadCache>TcPool;
tls_threadcache = TcPool.New();
}
cout << std::this_thread::get_id() << " " << tls_threadcache << endl;
return tls_threadcache->ApplySpace(size);
}
}
static void ConcurrentFree(void* ptr, size_t size) {
if(size>MAX_BYTE){
Span* span = PageCache::GetInst()->MapObjToSpan(ptr);
PageCache::GetInst()->_PageMtx.lock();
PageCache::GetInst()->RetrunPageCache(span);
PageCache::GetInst()->_PageMtx.unlock();
}
else {
assert(tls_threadcache != nullptr);
tls_threadcache->ReleaseSpace(ptr, size);
}
}
因为修改代码变化不大,为了避免冗余这里贴出链接
Github Gitee
2. 释放空间时不需要传大小
在之前,我们的高并发内存池释放空间时需要传大小,十分的不方便。
传大小来释放空间,为了判断这块空间是从堆上申请的还是通过三层缓存来申请的。还可以判断这块内存在ThreadCache和CentralCache哈希桶中的那个桶。
因为PageCache中,已经实现了页号到Span*的映射。 所以知道要释放的地址,根据地址计算页号,在根据页号找到Span,每个Span中自由链表上挂的空间大小都是相同的,所以在Span中添加属性,记录切好的内存块的大小。
struct Span {
PAGE_ID _PageID;
size_t _Num;
Span* _next;
Span* _prev;
size_t use_count;
void* FreeList;
bool IsUse;
size_t ObjectSize;
Span() :_PageID(0), _Num(0), _next(nullptr), _prev(nullptr), use_count(0), FreeList(nullptr), IsUse(false), ObjectSize(0) {}
};
- 在Central Cache中将PageCache的大块内存切分成小内存时,就将Span中 ObjectSize字段记录下来。等到最后释放时根据页号找Span在找 ObjectSize字段就知道要释放内存的大小了。
- 如果申请大于256KB,没有经过CentralCache,直接向PageCache申请,此时需要在申请后手动填写 ObjectSize字段。
注意: 大于256KB时访问PageCache的哈希桶,而PageCache只能被一个线程访问,PageCache中的Span指针与页号的映射也是能由一个人访问,所以要对获取Span映射的函数添加锁。
Span* PageCache::MapObjToSpan(void* obj) {
std::unique_lock<std::mutex>lock(_PageMtx);
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;
}
}
因为修改代码变化不大,为了避免冗余这里贴出链接
Github Gitee
|