内存管家是一个内存池,它实现了在多核多线程的环境下,效率较高的处理高并发的内存池。它由三层缓存结构组成,三层缓存分别为ThreadCache、CentralCache、PageCache。ThreadCache可以解决多核多线程环境下,锁的竞争问题;CentralCache可以均衡内存资源;PageCache可以解决内存碎片问题。
内存碎片:在内存分配的过程中会产生内存碎片,而内存碎片分为内碎片与外碎片。
- 内碎片:是指因为在内存中会因为内存对齐的原因,在内存分配过程中,为了要对齐到响应的对齐位置,会造成一定空间的浪费。
- 外碎片:是指在内存分配过程中,当需要一块内存时,可能是从一个大块的内存上切割下来的,不断的切割就会使3内存块变为小的内存块,最后导致无法申请大内存。
class ThreadCache
{
public:
//分配内存
void* Allocate(size_t size);
//释放内存
void Deallocate(void* ptr, size_t size);
//从中心缓存中获取内存对象
void* FetchFromCentralCache(size_t index, size_t size);
//当自由链表中的对象超过一次分配给threadcache的数量,则开始回收
void ListTooLong(FreeList* freelist, size_t byte);
private:
FreeList _freelist[NLISTS];// 创建了一个自由链表数组
};
对于FreeList这个类,我们只要封装一个普通指针和链表的长度即可。
ThreadCache申请内存:
Thread Cache释放内存:
// 控制内碎片浪费不要太大
//[1, 128] 8byte对齐 freelist[0,16)
//[129, 1024] 16byte对齐 freelist[17, 72)
//[1025, 8 * 1024] 64byte对齐 freelist[72, 128)
//[8 * 1024 + 1, 64 * 1024] 512byte对齐 freelist[128, 240)
// 也就是说对于自由链表数组只需要开辟240个空间就可以了
// 大小类
class ClassSize
{
public:
// align是对齐数
static inline size_t _RoundUp(size_t size, size_t align)
{
// 比如size是15 < 128,对齐数align是8,那么要进行向上取整,
// ((15 + 7) / 8) * 8就可以了
// 这个式子就是将(align - 1)加上去,这样的话就可以进一个对齐数
// 然后再将加上去的二进制的低三位设置为0,也就是向上取整了
// 15 + 7 = 22 : 10110 (16 + 4 + 2)
// 7 : 111 ~7 : 000
// 22 & ~7 : 10000 (16)就达到了向上取整的效果
return (size + align - 1) & ~(align - 1);
}
// 向上取整
static inline size_t RoundUp(size_t size)
{
assert(size <= MAXBYTES);
if (size <= 128)
{
return _RoundUp(size, 8);
}
if (size <= 8 * 128)
{
return _RoundUp(size, 16);
}
if (size <= 8 * 1024)
{
return _RoundUp(size, 128);
}
if (size <= 64 * 1024)
{
return _RoundUp(size, 512);
}
else
{
return -1;
}
}
//求出在该区间的第几个
static size_t _Index(size_t bytes, size_t align_shift)
{
//对于(1 << align_sjift)相当于求出对齐数
//给bytes加上对齐数减一也就是,让其可以跨越到下一个自由链表的数组的元素中
return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
}
//获取自由链表的下标
static inline size_t Index(size_t bytes)
{
//开辟的字节数,必须小于可以开辟的最大的字节数
assert(bytes < MAXBYTES);
//每个对齐区间中,有着多少条自由链表
static int group_array[4] = { 16, 56, 56, 112 };
if (bytes <= 128)
{
return _Index(bytes, 3);
}
else if (bytes <= 1024) //(8 * 128)
{
return _Index(bytes - 128, 4) + group_array[0];
}
else if (bytes <= 4096) //(8 * 8 * 128)
{
return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];
}
else if (bytes <= 8 * 128)
{
return _Index(bytes - 4096, 9) + group_array[2] + group_array[1] + group_array[0];
}
else
{
return -1;
}
}
};
// span结构
// 对于span是为了对于thread cache还回来的内存进行管理
// 一个span中包含了内存块
typedef size_t PageID;
struct Span
{
PageID _pageid = 0; //起始页号(一个span包含多个页)
size_t _npage = 0; //页的数量
Span* _next = nullptr; // 维护双向span链表
Span* _prev = nullptr;
void* _objlist = nullptr; //对象自由链表
size_t _objsize = 0; //记录该span上的内存块的大小
size_t _usecount = 0; //使用计数
};
将span组成的spanlist设计为一个双向链表,插入删除效率增大:
class SpanList
{
public:
// 双向循环带头结点链表
SpanList()
{
_head = new Span;
_head->_next = _head;
_head->_prev = _head;
}
Span* begin()
{
return _head->_next;
}
Span* end()
{
return _head;
}
bool Empty()
{
return _head == _head->_next;
}
void Insert(Span* cur, Span* newspan)
{
assert(cur);
Span* prev = cur->_prev;
//prev newspan cur
prev->_next = newspan;
newspan->_prev = prev;
newspan->_next = cur;
cur->_prev = newspan;
}
void Erase(Span* cur)
{
assert(cur != nullptr && cur != _head);
Span* prev = cur->_prev;
Span* next = cur->_next;
prev->_next = next;
next->_prev = prev;
}
void PushBack(Span* cur)
{
Insert(end(), cur);
}
void PopBack()
{
Span* span = end();
Erase(span);
}
void PushFront(Span* cur)
{
Insert(begin(), cur);
}
Span* PopFront()
{
Span* span = begin();
Erase(span);
return span;
}
// 给每一个Spanlist桶加锁
std::mutex _mtx;
private:
Span * _head = nullptr;
};
CentralCache申请内存:
// 采用饿汉模式,在main函数之前单例对象已经被创建
class PageCache
{
public:
// 获取单例模式
static PageCache* GetInstance()
{
return &_inst;
}
// 在SpanList中获取一个span对象,如果没有或者申请内存大于128页,则直接去系统申请
Span* NewSpan(size_t npage);
Span* _NewSpan(size_t npage);
// 获取从对象到span的映射
Span* MapObjectToSpan(void* obj);
// 从CentralCache归还span到Page,然后PageCache进行合并
void RelaseToPageCache(Span* span);
private:
// NPAGES是129,最大页数为128,也就是下标从1开始到128分别为1页到128页
SpanList _pagelist[NPAGES];
private:
PageCache() = default;
PageCache(const PageCache&) = delete;
PageCache& operator=(const PageCache&) = delete;
static PageCache _inst;
// 为了锁住SpanList,可能会存在多个线程同时来PageCache申请span
std::mutex _mtx;
std::unordered_map _id_span_map;
};
PageCache申请内存:
项目源码:https://github.com/TaoJun724/Project_Memory_House_Keeper