经过前面几个章节的设计的主体就已经完成了,接下来便是测试与优化的工作。
首先在设计上,有以下可以改进的地方:
一、项目中有大量的地方使用了new运算符
主要是在堆中开辟Span结构体,还有给每个线程在堆中开辟ThreadCache对象的地方。此时00.基础——简单定长内存池章节中的定长内存池就可以派上用场了,将高并发内存池中一些用到堆的工件地方,都向定长内存池申请即可。从定长内存池中申请内存要比调用new和malloc来得更快。
//定长内存池,用来存放高并发内存池本身需要用到的一些资源,比如Span结构体
template <class T>
class ObjectPool
{
public:
ObjectPool() :Tsize(sizeof(T))
{
size_t tenTsize = 10 * Tsize;
everyApplyPages = (tenTsize >> FIXEDPOOL_PAGESHIFT)+1;//设置每次向系统申请的页数,至少申请1页
}
T* New()
{
T* ptr = nullptr;
if (freelist)
{
ptr = (T*)freelist;
freelist = *(void**)freelist;
}
else
{
if (leftbytes < Tsize)
{
memory = (char*)systemalloc(everyApplyPages);//一次开辟everyApplyPages页
leftbytes = everyApplyPages << FIXEDPOOL_PAGESHIFT;
}
ptr = (T*)memory;
size_t objectsize = Tsize >= sizeof(void*) ? Tsize : sizeof(void*);
memory += objectsize;
leftbytes -= objectsize;
}
new(ptr)T;//定位new调用T的默认构造函数
return ptr;
}
void Delete(T* obj)
{
obj->~T();//调用析构函数
*(void**)obj = freelist;
freelist = (void*)obj;
}
private:
char* memory = nullptr;
int leftbytes = 0;
void* freelist = nullptr;
size_t Tsize=0;
size_t everyApplyPages = 0;
};
二、解决释放内存块时,要给出内存块大小的问题
每个内存块都是属于一个Span包的,既然如此根据要释放的地址就可以计算出内存块属于哪一页,再根据页号从std::unordered_map<PAGE_ID, Span*> Page_Span;中找到其所属的Span包。可以在Span包中添加一个成员变量,指明这个Span包内每个内存块的大小(大于256KB的情况,就是整个Span包的大小),如此一来,用户释放内存块的时候就不需要再自己传入内存块的大小了。
//内存包span,里面包含了连续页的内存,是中心资源层和页缓存资源层的重要结构
struct Span
{
PAGE_ID pageID = 0;//包中起始页在内存所有页中的编号(把内存视为一个个页,页编号计算方法为页起始地址/每页的大小)
size_t counts = 0;//包中页的数量
Span* prev = nullptr;
Span* next = nullptr;
void* freelist = nullptr;//里面存放着span内存包中被切好的内存块的“桶”
size_t usedBlocks = 0;//已经被分配出去的内存块数量
bool is_used = false;//标志该Span包是否已经被CentreCache获取
size_t objectSize = 0;//用来记录该Span包被切割成了多大的内存块,以方便回收内存时无需再传入申请的内存块的大小
static ObjectPool<Span> SpanPool;//用定长内存池存放所有的Span结构体
};