高并发内存池------threadcache
一、高并发内存池整体框架设计
1.需要考虑的问题
(1)性能问题。
(2)多线程环境下,锁竞争问题。
(3)内存碎⽚问题。
2.concurrent memorypool主要由以下3个部分构成:
1. threadcache:线程缓存是每个线程独有的,⽤于⼩于256KB的内存的分配,线程从这⾥申请内存不需要加锁,每个线程独享⼀个cache,这也就是这个并发线程池⾼效的地⽅。
2. central cache:中⼼缓存是所有线程所共享,threadcache是按需从centralcache中获取的对 象。centralcache合适的时机回收threadcache中的对象,避免⼀个线程占⽤了太多的内存,⽽其 他线程的内存吃紧,达到内存分配在多个线程中更均衡的按需调度的⽬的。centralcache是存在竞 争的,所以从这⾥取内存对象是需要加锁,⾸先这⾥⽤的是桶锁,其次只有threadcache的没有内 存对象时才会找centralcache,所以这⾥竞争不会很激烈。
3. pagecache:⻚缓存是在centralcache缓存上⾯的⼀层缓存,存储的内存是以⻚为单位存储及分 配的,centralcache没有内存对象时,从pagecache分配出⼀定数量的page,并切割成定⻓⼤⼩ 的⼩块内存,分配给centralcache。当⼀个span的⼏个跨度⻚的对象都回收以后,pagecache会 回收centralcache满⾜条件的span对象,并且合并相邻的⻚,组成更⼤的⻚,缓解内存碎⽚的问题
二、高并发内存池----threadcache
thread cache是哈希桶结构,每个桶是⼀个按桶位置映射⼤⼩的内存块对象的⾃由链表。每个线程都会有⼀个threadcache对象,这样每个线程在这⾥获取对象和释放对象时是⽆锁的。
1.申请内存
1. 当内存申请size<=256KB,先获取到线程本地存储的threadcache对象,计算size映射的哈希桶自由链表下标i。
2. 如果⾃由链表_freeLists[i]中有对象,则直接Pop⼀个内存对象返回。
3. 如果_freeLists[i]中没有对象时,则批量从centralcache中获取⼀定数量的对象,插⼊到⾃由链表 并返回⼀个对象。
2.释放内存
1. 当释放内存⼩于256k时将内存释放回threadcache,计算size映射⾃由链表桶位置i,将对象Push 到_freeLists[i]。
2. 当链表的⻓度过⻓,则回收⼀部分内存对象到centralcache。
#pragma once#include"Common.h"class ThreadCache
{
public://申请和释放对象内存void* Allocate(size_t size){assert(size <= MAX_BYTES);size_t alignSize = Sizeclass::RoundUp(size);//对齐数size_t index = Sizeclass::Index(size);if(!_freeLists[index].Empty()){return _freeLists[index].Pop();}else{return FetchFromCentralCache(index, alignSize);}}void Deallocate(void* ptr, size_t size){assert(ptr);assert(size <= MAX_BYTES);//找对映射的自由链表桶,对象插入进入size_t index = Sizeclass::Index(size);_freeLists[index].Push(ptr);}//从中心缓存获取对象void* FetchFromCentralCache(size_t index, size_t size){return nullptr;}
private:FreeList _freeLists[NFREELIST];
};// TLS thread local storage
thread_local ThreadCache* pTLSThreadCache = nullptr;
//thread_local int pTLSThreadCache = 0;
#pragma once#include <iostream>
#include <cstdint>
#include <thread>
#include <vector>
#include<mutex>#include <time.h>
#include <assert.h>// ⼩于等于MAX_BYTES,就找thread cache申请
// ⼤于MAX_BYTES,就直接找page cache或者系统堆申请// thread cache 和central cache⾃由链表、哈希桶的表⼤⼩
static const size_t MAX_BYTES = 256 * 1024;
static const size_t NFREELIST = 208;#ifdef _WIN64
typedef unsigned long long PAGE_ID;
#elif _WIN32
typedef size_t PAGE_ID;
#else
// Linux
typedef uintptr_t PAGE_ID; // 使用 uintptr_t 以确保正确的大小
#endif// 管理切分好的小对象自由链表
// 获取内存对象中存储的头4 or 8字节值,即链接的下⼀个对象的地址
static void *&NextObj(void *obj)
{return *(void **)obj;
}
class FreeList
{
public:void Push(void *obj){// 头插NextObj(obj) = _freeList;_freeList = obj;}void *Pop(){assert(_freeList);// 头删void *obj = _freeList;_freeList = NextObj(obj);return obj;}bool Empty(){return _freeList == nullptr;}private:void *_freeList = nullptr;
};// 管理对象大小的对齐映射关系
class Sizeclass
{
public:// 整体控制在最多10%左右的内碎片浪费// [1,128] 8byte对齐 freelist[0,16)// [128+1,1024] 16byte对齐 freelist[16,72)// [1024+1,8*1024] 128byte对齐 freelist[72,128)// [8*1024+1,64*1024] 1024byte对齐 freelist[128,184)// [64*1024+1,256*1024] 8*1024byte对齐 freelist[184,208)static size_t _RoundUp(size_t bytes, size_t alignNum){size_t alignSize;if (bytes % alignNum != 0){alignSize = (bytes / alignNum + 1) * alignNum;}else{alignSize = bytes;}return alignSize;}static inline size_t RoundUp(size_t size){if (size <= 128){return _RoundUp(size, 8);}else if (size <= 1024){return _RoundUp(size, 16);}else if (size <= 8 * 1024){return _RoundUp(size, 128);}else if (size <= 64 * 1024){return _RoundUp(size, 1024);}else if (size <= 256 * 1024){return _RoundUp(size, 8 * 1024);}else{assert(false);return -1;}}static 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){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从中心缓存获取多少个static size_t NumMoveSize(size_t size){assert(size > 0);// [2, 512],一次批量移动多少个对象的(慢启动)上限值// 小对象一次批量上限高// 大对象一次批量上限低int num = MAX_BYTES / size;if(num < 2)num = 2;if(num > 512)num = 512;return num;}
};// 管理多个连续页大块内存跨度结构
struct Span
{PAGE_ID _pageId = 0; // 大块内存起始页的页号size_t _n = 0; // 页的数量Span *_next = nullptr; // 双向链表的形式Span *_prev = nullptr;size_t _useCount = 0; // 切好的小块内存,被分配给ThreadCache 计数void *_freeList = nullptr; // 切好的小块内存,自由链表
};// 带头双向循环链表
class SpanList
{
public:SpanList(){_head = new Span;_head->_next = _head;_head->_next = _head;}void Insert(Span *pos, Span *newSpan){assert(pos);assert(newSpan);Span *prev = pos->_prev;// prev newspan posprev->_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;prev->_prev = prev;}
private:Span *_head;
public:std::mutex _mtx;//桶锁
};