当前位置: 首页 > news >正文

高并发内存池|二、Common

二、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)的 & 结果结果
13200001 01000001 000016
14210001 01010001 000016
15220001 01100001 000016
16230001 01110001 000016
17240001 10000001 100024

2. 索引位置实现

注意 _Index() 中的索引计算公式:

((bytes + (1 << align_shift) - 1) >> align_shift) - 1;

这个公式是在将不同大小的内存块放进对应大小的桶的索引中,即在计算索引偏移量,以 8 对齐为例:

bytesbytes + 7>> 3 (除以8)减 1最终结果(索引)
18 (0000 1000)100
1623 (0001 0111)211
1724 (0001 1000)322
3138 (0010 0110)433
3239 (0010 0111)433
3340 (0010 1000)544

要注意,这个索引结果是相对于一个对齐规则的偏移量,在 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;
};

相关文章:

  • 【JavaWeb】JDBC
  • 如何利用内网穿透实现Cursor对私有化部署大模型的跨网络访问实践
  • java中sleep()和wait()暂停线程的区别
  • [Java实战]Spring Boot整合Elasticsearch(二十六)
  • 大模型微调步骤整理
  • 第9章 组件及事件处理
  • Mac 在恢复模式下出现 旋转地球图标 但进度非常缓慢
  • Oracle 内存优化
  • java中的Servlet3.x详解
  • sparkSQL读入csv文件写入mysql
  • 10.8 LangChain三大模块深度实战:从模型交互到企业级Agent工具链全解析
  • 多模态大语言模型arxiv论文略读(八十一)
  • SuperYOLO:多模态遥感图像中的超分辨率辅助目标检测之论文阅读
  • 贪心算法应用:最大匹配问题详解
  • 算法岗实习八股整理——深度学习篇(不断更新中)
  • 软件工程各种图总结
  • MySQL开发规范
  • 互联网大厂Java面试:从Spring到微服务的深度探讨
  • 大模型deepseek与知识图谱的实践
  • 【数据结构】2-3-3单链表的查找
  • 左手免费午餐右手花开岭,邓飞14年公益之路的中国贡献
  • 北方今年首场高温过程开启,西北华北黄淮多地最高或达40℃
  • 《缶翁的世界》首发:看吴昌硕等湖州籍书画家的影响
  • 复旦一校友捐赠1亿元,却不留名
  • 英国警方再逮捕一名涉嫌参与首相住宅纵火案嫌疑人
  • 乌称苏梅州一公共汽车遭俄军袭击,致9死4伤