二、Common
这部分主要存放高并发内存池中三个 cache 都会使用到的共同的结构。如内存对齐规则、自由链表结构、SpanList 结构、内存池向堆申请内存等功能都在这部分实现。
1. 对齐规则实现
注意 _RoundUp()
中的对齐计算公式,是一个经典的内存对齐技巧:
((bytes + alignNum - 1) & ~(alignNum - 1));
例如,当 alignNum
为 8 时,~(alignNum - 1)
就是 7 取反,它的二进制为 …… 1111 1000
,此时,这个数与任何一个大于 8 的正整数进行与操作,其结果都会是 8 的倍数。
而 (bytes + alignNum - 1)
的作用,就是要确定究竟要取 8 的多少倍,实际上,它就是在进行**“向上取整”,因为任何一个非零自然数加上 7,其结果都会大于等于 8,转换为二进制后,这个结果一定会有高于第四位的 1
,而进行与操作后,小于第四位的二进制位,会被置零,即把低位对齐部分清零,保留高位(第四位)**。
原始 bytes | 加7后 | 二进制表示 | 与 ~7(11111000)的 & 结果 | 结果 |
---|---|---|---|---|
13 | 20 | 0001 0100 | 0001 0000 | 16 |
14 | 21 | 0001 0101 | 0001 0000 | 16 |
15 | 22 | 0001 0110 | 0001 0000 | 16 |
16 | 23 | 0001 0111 | 0001 0000 | 16 |
17 | 24 | 0001 1000 | 0001 1000 | 24 |
2. 索引位置实现
注意 _Index()
中的索引计算公式:
((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
这个公式是在将不同大小的内存块放进对应大小的桶的索引中,即在计算索引偏移量,以 8 对齐为例:
bytes | bytes + 7 | >> 3 (除以8) | 减 1 | 最终结果(索引) |
---|---|---|---|---|
1 | 8 (0000 1000) | 1 | 0 | 0 |
16 | 23 (0001 0111) | 2 | 1 | 1 |
17 | 24 (0001 1000) | 3 | 2 | 2 |
31 | 38 (0010 0110) | 4 | 3 | 3 |
32 | 39 (0010 0111) | 4 | 3 | 3 |
33 | 40 (0010 1000) | 5 | 4 | 4 |
要注意,这个索引结果是相对于一个对齐规则的偏移量,在 Index()
中 group_array
存储的是每个对齐规则所占的索引数目,因此为了得到全局索引,除 8 对齐以外的对齐规则,还要加上前面的对齐规则所占用的索引数目,才能得到真正的索引。
3. PushRange()
PushRange()
是头插一个自由链表,如过在调用这个函数前,自由链表 _freeList
结构如下:
_freeList -> A -> B -> C -> ...
传入 PushRange()
的链表结构为:
start -> X -> Y -> Z (end)
执行代码后,Z(end)
的下一个就被设置为 _freeList
指向的内容,即 A
,然后再更新 _freeList
指向的位置,最终完成了链表的头插:
X -> Y -> Z ->A -> B -> C -> ...
4. 避免使用malloc
由于高并发内存池的作用,就是在高并发的场景下,代替 malloc()
来给线程分配内存空间的,所以在代码中要避免使用 malloc()
和 new
来创建对象。我们可以使用 VirtualAlloc()
(Windows 环境下)或者 mmap()
(Linux 环境下)来绕过 malloc()
向系统申请内存空间:
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#elif __linux__
size_t size = page << 13;
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);#endif
if (ptr == nullptr)
{throw std::bad_alloc();
}
return ptr;
}template<class T>
class ObjectPool
{
public:T* New(){T* object = nullptr;//优先使用还回来的空间if (_freeList){void* next = *((void**)_freeList);object = (T*)_freeList;_freeList = next;}//如果大空间不足if (_restBytes < sizeof(T)){_restBytes = 128 * 1024; //一次申请128Kb的空间_memory = (char*)SystemAlloc(_restBytes >> 13); //128Kb右移13位相当于16页if (_memory == nullptr){throw std::bad_alloc();}}//从申请的大空间中截一块出来object = (T*)_memory;size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objSize;_restBytes -= objSize;//定位new,显示调用T的构造函数new(obj)T;return obj;}void Delete(T* obj){obj->~T();*(void**)obj = _freeList;_freeList = obj;//把空间还回来}
private:char* _memory = nullptr; //char类型为1字节大小方便申请任意大小的内存size_t _restBytes = 0; //记录大内存在切分后的剩余比特数void* _freeList = nullptr;
};
5. SpanList
SpanList 是一个双向带头节点的双向链表结构,它的大部分功能和双向链表无异,只是在删除节点时,不会真的释放此处的空间,因为内存池还要回收内存进行再分配。
5.1 Span
Span 作为 SpanList 中的一个节点,主要包含以下内容:
class Span
{
public:PAGE_ID _pageID = 0; //大块内存起始页的页号size_t _pageNum = 0; //页的数量Span* _next = nullptr;Span* _prev = nullptr;bool _isUse = false;size_t _objSize = 0; //切好的小对象大小size_t _useCount = 0; //小内存块分配给thread cache的计数void* _freeList = nullptr; //小内存块的自由链表
};
5.2 SpanList代码
class SpanList
{
public:SpanList(){static ObjectPool<Span> spanPool;_head = spanPool.New();_head->_next = _head;_head->_prev = _head;}Span* Begin(){return _head->_next;}Span* End(){return _head;}bool Empty(){return _head->_next == _head;}void PushFront(Span* span){Insert(Begin(), span);}Span* PopFront(){Span* front = _head->_next;Erase(front);return front;}void Insert(Span* pos, Span* newSpan){assert(pos);assert(newSpan);Span* prev = pos->_prev;prev->_next = newSpan;newSpan->_prev = prev;newSpan->_next = pos;pos->_prev = newSpan;}void Erase(Span* pos){assert(pos);assert(pos != _head);Span* prev = pos->_prev;Span* next = pos->_next;prev->_next = next;next->_prev = prev;}
private:Span* _head;
public:std::mutex _mtx;
};
6. 完整代码
#pragma once
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <time.h>
#include <assert.h>
#ifdef _WIN32
#include <Windows.h>
#endif
#ifdef _WIN64
typedef unsigned long long PAGE_ID;
#elif _WIN32 typedef size_t PAGE_ID;
#endifstatic const size_t MAX_BYTES = 256 * 1024;
static const size_t NFREELIST = 208; //为thread cache的哈希桶的数量
static const size_t NPAGES = 129;
static const size_t PAGE_SHIFT = 13;inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#elif __linux__
size_t size = kpage << 13;
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);#endifif (ptr == nullptr)
{DWORD error = GetLastError();printf("VirtualAlloc failed! Error code: %lu\n", error);throw std::bad_alloc();
}
return ptr;
}inline static void SystemFree(void* ptr)
{
#ifdef _WIN32VirtualFree(ptr, 0, MEM_RELEASE);
#else// sbrk unmmap等
#endif
}#include "ObjectPool.h"static void*& NextObj(void* obj)
{//obj相当于指针的指针return *(void**)obj;
}class FreeList
{
public:void Push(void* obj){assert(obj);//头插NextObj(obj) = _freeList;_freeList = obj;++_size;}//头插一串小内存void PushRange(void* start, void* end,size_t size){NextObj(end) = _freeList;_freeList = start;_size += size;}void* Pop(){//头删void* obj = _freeList;_freeList = NextObj(obj);--_size;return obj;}bool Empty(){return _freeList == nullptr;}//_maxSize会在thread cache申请内存时增加size_t& MaxSize(){return _maxSize;}size_t Size(){return _size;}void PopRange(void*& start, void*& end, size_t rangeSize){assert(rangeSize <= _size);start = _freeList;end = start;for (size_t i = 0; i < rangeSize - 1; i++){//走到最后一个节点end = NextObj(end);}_freeList = NextObj(end);NextObj(end) = nullptr;_size -= rangeSize;}private:void* _freeList = nullptr;size_t _maxSize = 1;size_t _size = 0;
};class SizeClass
{
public:static inline size_t _RoundUp(size_t bytes, size_t alignNum){return ((bytes + alignNum - 1) & ~(alignNum - 1));}static inline size_t RoundUp(size_t size){if (size <= 128) //8byte{return _RoundUp(size, 8);}else if (size <= 1024) //16byte{return _RoundUp(size, 16);}else if (size <= 8192) //128byte{return _RoundUp(size, 128);}else if (size <= 65535) //1024byte{return _RoundUp(size, 1024);}else if (size <= 262144) //8*1024byte{return _RoundUp(size, 8192);}else{assert(false);return -1;}}static inline size_t _Index(size_t bytes, size_t align_shift){return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;}static inline size_t Index(size_t bytes){assert(bytes <= MAX_BYTES);static int group_array[4] = { 16,56,56,56 };if (bytes <= 128){return _Index(bytes, 3);}else if (bytes <= 1024){return _Index(bytes - 128, 4) + group_array[0];}else if (bytes <= 8 * 1024){return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];}else if (bytes <= 64 * 1024){return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1] + group_array[0];}else if (bytes <= 256 * 1024)//这个区间有24个桶{return _Index(bytes - 64 * 1024, 13) + group_array[3] + group_array[2] + group_array[1] + group_array[0];}else{assert(false);}return -1;}//一次thread cache从central cache中获得多少个static size_t NumMoveSize(size_t size){assert(size > 0);int num = MAX_BYTES / size;if (num < 2){num = 2;}if (num > 512){num = 512;}return num;}static size_t NumMovePage(size_t size){size_t num = NumMoveSize(size);size_t npage = num * size;npage >>= PAGE_SHIFT;if (npage == 0){npage = 1;}return npage;}
};class Span
{
public:PAGE_ID _pageID = 0; //大块内存起始页的页号size_t _pageNum = 0; //页的数量Span* _next = nullptr;Span* _prev = nullptr;bool _isUse = false;size_t _objSize = 0; //切好的小对象大小size_t _useCount = 0; //小内存块分配给thread cache的计数void* _freeList = nullptr; //小内存块的自由链表
};class SpanList
{
public:SpanList(){static ObjectPool<Span> spanPool;_head = spanPool.New();_head->_next = _head;_head->_prev = _head;}Span* Begin(){return _head->_next;}Span* End(){return _head;}bool Empty(){return _head->_next == _head;}void PushFront(Span* span){Insert(Begin(), span);}Span* PopFront(){Span* front = _head->_next;Erase(front);return front;}void Insert(Span* pos, Span* newSpan){assert(pos);assert(newSpan);Span* prev = pos->_prev;prev->_next = newSpan;newSpan->_prev = prev;newSpan->_next = pos;pos->_prev = newSpan;}void Erase(Span* pos){assert(pos);assert(pos != _head);Span* prev = pos->_prev;Span* next = pos->_next;prev->_next = next;next->_prev = prev;}
private:Span* _head;
public:std::mutex _mtx;
};