高并发内存池:Part-4——项目优化、调试性能分析、项目复盘
bit::Shadow✧(≖ ◡ ≖✿目录嵌入定长内存池大于256KB处理龙级Bug分析性能分析测试性能窗口调出流程调用方/被调用方火焰图更加形象、清晰优化前的性能测试基数树优化二级基数树二级基数树图示项目结果提效不足项目复盘ConCurrentAlloc()全流程ConCurrentFree()全流程gitee链接优化完整版高并发内存池复习时同步博客复习嵌入定长内存池使用定长内存池的New()和Delete()用于取代使用new、free的堆申请释放。在PageCache内新定义成员ObjectPoolSpan obPol;像void* ConCurrentAlloc(size_t size) { //size_t a SizeClass::Index(256 * 1024);//a207 if (!pTLSThreadCache) { //优化1.使用定长内存池取代堆new ObjectPoolThreadCache obPol; pTLSThreadCache obPol.New(); //pTLSThreadCache- } return pTLSThreadCache-Alloc(size); }三级缓冲区GetSpans(size_t pageNum)Span* PageCache::GetSpans(size_t pageNum) { if (pageNum 128) { //大于128页堆申请 //优化1.使用定长内存池取代堆new Span* span obPol.New(); void* ptr SizeClass::SystemAlloc(pageNum); span-_n pageNum; span-_pageId (PAGE_ID)ptr PAGE_SHIFT; _IDSpanMap.set(span-_pageId, span);//用于大于128的回收 return span; }//是否应该将此Span加入Map?无用——不涉及合并使用 //三级缓冲区获取span (128) //1.直接定位 2.向下找 3.VirtualAlloc()堆区申请 Span* span PageCache::GetInstance()-_spanList[pageNum].Begin(); if (span-_nextSpan ! span) { //PageCache拥有span非空链表 //SpanList的头删 PageCache::GetInstance()-_spanList[pageNum].ErasePos(span); span-_isUse true; return span;//这也是ErasePos内不能delete的原因。要向上供给,这块内存属于 } //[pageNum]下没有span //2.在PageCache内向下找 for(int i pageNum1;i 129;i) { Span* _span PageCache::GetInstance()-_spanList[i].Begin(); if (_span-_nextSpan ! _span) { // 找到了需pageNum块多出了i-pageNum块 // 考虑到此部分代码在3.情况下的复用可以额外封装也可以使用巧妙的递归解决。 // 头删 PageCache::GetInstance()-_spanList[i].ErasePos(_span); //分割与插入 /*Span* insertSpan (Span*)((PAGE_ID)_span pageNum PAGE_SHIFT); insertSpan-_nextSpan insertSpan; insertSpan-_prevSpan insertSpan;*/ //强制类型转换造成的内存越界访问 Span* insertSpan obPol.New();//堆申请!!! 照应本文件line[121] ///为什么要堆申请 //// 堆申请的作用1.改变了生命周期(可被址定位替代)2.额外空间由堆(多线程一个进程区域)提供 //// 若使用址定位无法访问类内部空间解决办法是... //// 不使用堆函数结束后insertSpan无法被访问。 insertSpan-_n i - pageNum; insertSpan-_pageId _span-_pageIdpageNum; for (PAGE_ID j 0; j insertSpan-_n; j) { //仅头尾_n初始化即可 PageCache::GetInstance()-_IDSpanMap.set(insertSpan-_pageId j, insertSpan); } PageCache::GetInstance()-_spanList[i - pageNum].HeadInsert(insertSpan);//剩余块头插 _span-_n pageNum; //_span-_pageId pageNum;//你加你妈呢 for (PAGE_ID j 0; j _span-_n; j) { //仅头尾_n初始化即可 PageCache::GetInstance()-_IDSpanMap.set(_span-_pageId j, _span); } _span-_isUse true; return _span; } } //3.[i]向下全空向系统申请128页--128*8K void* ptr SizeClass::SystemAlloc(128);//无需检查失败VirtualAlloc() //Span* test new Span; Span* m128span obPol.New(); //Span* m128span (Span*)ptr;//调用自动生成的拷贝赋值无法自己指向自己 //// //m128span-_nextSpan m128span; //m128span-_prevSpan m128span; //m128span-_n 128; //m128span-_pageId (PAGE_ID)m128span PAGE_SHIFT; m128span-_pageId (PAGE_ID)ptr PAGE_SHIFT; m128span-_n 128; _spanList[128].HeadInsert(m128span); for(int i 0;i 128;i) PageCache::GetInstance()-_IDSpanMap.set(m128span-_pageIdi, m128span); return GetSpans(pageNum); }大于256KB处理256KB相当于32*8KB32页所以存在弹性区间若是(32, 128]直接向三级缓冲区申请与释放若是大于128页及以上就应该向堆直接申请与回收。完善后的Allocsize_t sizevoid* ThreadCache::Alloc(size_t size) { //线程申请指定字节数 1.对齐数2.获取空间自自由链表/FetchFromCentralCache assert(size 0);//为0及负数不申请 //对齐找桶 //不对Index的确定是基于对齐数那么你当初在设计时就无需考虑余数情景 size_t AlignSize SizeClass::RoundUp(size); void* ret nullptr; if (size (1 18)) { // 256KB //1. 128*8KB 找三级缓冲区切割、合并、返回到三级缓冲区、向前、向后合并 //2. 128*8KB系统堆申请、返还 size_t npage SizeClass::NumMovePage(SizeClass::RoundUp(size)); PageCache::GetInstance()-_mtx.lock(); Span* span PageCache::GetInstance()-GetSpans(npage); PageCache::GetInstance()-_mtx.unlock(); //return (void*)span; ret (void*)((PAGE_ID)span-_pageId PAGE_SHIFT); } else { size_t i SizeClass::Index(size); //256KB申请 if (!_freeList[i].empty()) { //自由链表非空 ret _freeList[i].Pop(); } else { //二级缓存获取 ret FetchFromCentralCache(i, AlignSize); } } ////优化2.去除Free时必须传入size的毛病 //unordered_mapvoid*, size_t rtMap.Push(ret, size); return ret; }完善后的ConCurrentFree(void* ptr):void ConCurrentFree(void* ptr) { // 256KB //1. 128*8KB 找三级缓冲区切割、合并、返回到三级缓冲区、向前、向后合并 //2. 128*8KB系统堆申请、返还 Span* span PageCache::GetInstance()-MapGetSpan((PAGE_ID)ptr PAGE_SHIFT); ///为什么不需要加锁因为线程独立虽然使用了PageCache内的Map其内部的定位因为ptr一定是先前Alloc过的。 if (span-_n 32) { //释放空间 pTLSThreadCache-DeAlloc(ptr);//暂时性实现size后期无 return; } else { std::mutex mtx; std::lock_guardstd::mutex lock(mtx); if (span-_n 32)//256KB { if (span-_n 128)//三级缓冲区回收 PageCache::GetInstance()-RetSpan2Page(span); else//系统堆回收 SizeClass::SystemDealloc(ptr); } } }龙级Bug分析注这是项目结束后的测试阶段测出的bug。反思在能够跑通时就应该立即进行尽可能充分的测试“不充分的测试就相当于没有测试”。一现象双向带头链表内无限循环寻找位于二级缓冲区内的特定Span块。[108] void CentralCache::RetList2Spans(FreeList _freeList, size_t size);解决方案在二级缓冲区满后应该先Erase后插入三级缓冲区。抓栈帧、条件断点执行。调用堆栈用于监视位于断点发生之上的函数块内的监视变量条件断点指专门为特殊情况设立的一个断点当满足此情况该断点才会成立。Span* PageCache::MapGetSpan(PAGE_ID pgID) { //条件断点 if (!_IDSpanMap[pgID]) { int a 0; //此处加断点 } if (_IDSpanMap.find(pgID) _IDSpanMap.end()) assert(false); return (Span*)_IDSpanMap.get(pgID);//课件中页号为什么右移 必须右移源于_pageID的形成 }性能分析1.Debug下分析2.不能凭感觉对项目各模块分析。而是要使用性能分析工具。3.性能瓶颈点一般在特定的某个点4.测试性能的例子一定要选择好测试完整测试/测试哪个模块就要调用哪个测试逻辑。测试性能窗口调出流程处理警告⚠️配置链接器属性调用方/被调用方进入“调用方/被调用方”模块查看以函数为单位的调用消耗左侧“正在调用的函数”指明当前模块被调用的函数占整体权重自main开始的测试用例调用整体的百分比。中央“当前函数”分为当前函数占总体比当前函数体块向下调用函数不被计入占整体比。因此第二个参数永远小于等于第一个参数右侧“调用的函数”指当前函数内的向下调用函数占总体之比。即右侧占比之和中央“函数体”占比正在调用函数占整体之比我们自main进入实际是ConcurrentPool.exe进入查看得知CentralCache::FetchListNodes()占比最大。FetchListNodes内可见“函数体”结构占比最大说明当前函数消耗大的原因很大程度上是自身的设计而非向下调用的销毁导致的。因此我们对其内部分析得知是锁的原因优化此锁就成为了优化此项目的重要着力点。火焰图更加形象、清晰清晰的展示了调用流程下的调用逻辑与各模块占比也很容易分析出CentralCache::FetchList内的消耗就是优化重点。优化前的性能测试测试结果是208 140 142倍的提速。实际上若我们再提高测试轮次将尽可能的踩中“同一个span上的重叠存活span的归还”实际上我们设计的高并发内存池是优于malloc下的申请释放逻辑的本项目下大概率超越malloc、free的内存管理。基数树优化网上找的模板手动改造二级基数树template int BITS//19 51 class CardinalTree2 { private: // Put 32 entries in the root and (2^BITS)/32 entries in each leaf. static const int ROOT_BITS 5; static const int ROOT_LENGTH 1 ROOT_BITS; static const int LEAF_BITS BITS - ROOT_BITS;//19-5 51-5 static const int LEAF_LENGTH 1 LEAF_BITS;//2^14/46 // Leaf node struct Leaf { //此处void*保留即可模板 void* values[LEAF_LENGTH];//二级区指针数组低14/46位 32Nodes下各子节点14位/46位 }; Leaf* root_[ROOT_LENGTH]; // 一级区高5位 Pointers to 32 child nodes //void* (*allocator_)(size_t); // 函数指针声明它声明了一个指向函数的指针 public: typedef uintptr_t Number;//uintptr_t:安全地 指针-整数 转换类型 /*explicit CardinalTree2(void* (*allocator)(size_t)) { allocator_ allocator; memset(root_, 0, sizeof(root_)); }*/ explicit CardinalTree2() { memset(root_, 0, sizeof(root_)); PreallocateMoreMemory(); } void* get(Number k) const { const Number i1 k LEAF_BITS;// 14 仅计_pgId高5位 const Number i2 k (LEAF_LENGTH - 1);// 仅保留传入地址k的低13位 if ((k BITS) 0 || root_[i1] nullptr) return nullptr; return root_[i1]-values[i2]; } void set(Number k, void* v) { const Number i1 k LEAF_BITS; const Number i2 k (LEAF_LENGTH - 1); assert(i1 ROOT_LENGTH); root_[i1]-values[i2] v; } bool Ensure(Number start, size_t n) { for (Number key start; key start n - 1;) { const Number i1 key LEAF_BITS; // Check for overflow if (i1 ROOT_LENGTH) return false; // Make 2nd level node if necessary if (root_[i1] nullptr) { ObjectPoolLeaf obp; Leaf* leaf obp.New(); if (leaf nullptr) return false; memset(leaf, 0, sizeof(*leaf)); root_[i1] leaf; } // Advance key past whatever is covered by this leaf node key ((key LEAF_BITS) 1) LEAF_BITS; } return true; } void PreallocateMoreMemory() { // Allocate enough to keep track of all possible pages Ensure(0, 1 BITS); } };二级基数树图示将全部的_IDSpanMap替换为这样一颗基数树有效map查询低下的问题。项目结果提效v.push_back(ConCurrentAlloc((16 i) % 8192 1)); //alloc数动态变化尽量减少重复4个线程并发执行10轮次每轮申请2000~9000次提效300%(±50%)4个线程并发执行10轮次每轮释放2000~9000次提效3000%(±50%)。综合提效在400%~500%的动态区间。不足当申请次数4线程并发申请10轮每轮申请/释放一万次95%及以上次数有概率出现崩溃主要原因是Span块在定长内存池的回收与GetSpans中切分时_n的标记矛盾问题。此时的整体本项目旨在提高管理内存的效率所以说稳定性的重要程度要大于效率的提升的重要程度此项目的问题在于极高并发下的崩溃问题主要问题在于Span的并发问题。但是并发越高此内存池带来的效率提升越明显甚至达到了10000%倍的效率提升。做此项目的目的在于思考设计逻辑体会工程级项目的设计流程查缺补漏。项目复盘1.各个模块要做到鲁棒性极强。不要造成看似无关的设计像双向链表删除的悬挂2.设计原则高内聚、低耦合代码的健壮性 少量的效率优化(像仅首尾页标注)带来的收益。3.锁分析。亮点1三级缓存架构 线程局部缓存Thread Local Storage→ 线程自己缓存对象无锁分配只有缓存不足才加锁去Central Cache亮点2慢启动 批量交换还记得 min 和 NumMoveSize 吗→ 从拿1个开始逐渐翻倍到上限512个减少锁竞争又不浪费亮点3Central Cache 的 Span 切割与回收哈希桶双向链表双端CAS→ 空闲Span从头部拿非空闲从尾部拿减少碎片亮点4Page Cache 的基数树反查CardinalTree2→ 释放对象时 O(1) 找到所属 Span惰性分配节省内存并发内存池C11约 1000 行三级缓存架构Thread CacheTLS 无锁分配→ Central Cache互斥锁批量交换→ Page Cache大块内存切割与回收将 malloc 分配耗时降低约 300%慢启动 批量获取机制动态调整单次获取对象数量减少锁竞争基于哈希桶 双向链表管理 Span空闲/非空闲双端出队降低内存碎片基数树2级 radix tree实现 pageId→Span 的 O(1) 反查惰性分配节省内存通过 Valgrind/ASan 无泄漏检查多线程压测无崩溃感谢支持长期连载欢迎关注

相关新闻

公众号文章被判定AI生成后推荐量掉了一半?我是这样把流量拉回来的。

公众号文章被判定AI生成后推荐量掉了一半?我是这样把流量拉回来的。

公众号文章被判定AI生成后推荐量掉了一半?我是这样把流量拉回来的。 那天早上七点多我照例点开后台,前一天那篇的推荐量停在四位数,往常这个时间该是它的三四倍。我以为是选题不行,第二天换了个更稳的题材,结果更低。第…

2026/10/14 4:45:26 阅读更多 →
MySQL配置文件优化与关键参数调优指南

MySQL配置文件优化与关键参数调优指南

1. MySQL配置文件深度解析作为关系型数据库的标杆产品,MySQL的配置文件堪称数据库系统的"中枢神经"。我管理过上百个MySQL实例,深刻体会到配置文件就像数据库的基因编码——相同的软件安装包,通过不同的配置参数组合,可…

2026/10/11 5:08:51 阅读更多 →
从零制作纯净Windows安装U盘:官方镜像+Rufus工具全流程指南

从零制作纯净Windows安装U盘:官方镜像+Rufus工具全流程指南

1. 为什么你还需要一个“纯净”的启动U盘?如果你在搜索引擎里输入“重装系统”,跳出来的结果十有八九会指向各种“一键重装”工具。这些工具确实方便,把下载、制作、安装打包成一步,但代价往往是捆绑了你不想要的软件、修改了系统…

2026/10/13 22:20:47 阅读更多 →

最新新闻

JavaScript数组方法实战指南:从性能陷阱到函数式流水线

JavaScript数组方法实战指南:从性能陷阱到函数式流水线

1. 为什么我坚持不用 for 循环写数组逻辑——从一次线上事故说起去年在某跨平台系统重构中,团队一位刚转正的前端同学负责一个商品列表页的数据处理模块。他用三层嵌套 for 循环遍历商品数组、再遍历每个商品的 SKU 列表、最后对每个 SKU 做价格校验和库存标记。上线…

2026/10/15 0:21:17 阅读更多 →
PPT抽奖程序名单抽取:用VBA实现不重复随机抽奖与避坑指南

PPT抽奖程序名单抽取:用VBA实现不重复随机抽奖与避坑指南

简介:这是一个基于VBA宏的PPT抽奖程序模板,专为活动现场的名单抽取与随机滚动展示而设计,适合PPT进阶用户、活动策划者以及对VBA窗体编程感兴趣的读者。压缩包仅含1个PPT演示文稿,大小51KB,轻量便携。程序以按钮触发“…

2026/10/15 0:21:17 阅读更多 →
新手教师班级管理:先立规则再谈感情,有原则的善良才有威信

新手教师班级管理:先立规则再谈感情,有原则的善良才有威信

1. 先想清楚:新手教师的“善良”为什么会变成“软弱”刚站上讲台的新手教师,大多带着一腔热情和对学生的善意。我见过不少新老师,第一节课就笑着跟学生说“咱们以后是朋友”,学生作业没写,他摆摆手说“下次注意”&…

2026/10/15 0:21:17 阅读更多 →
并行计算实验报告高分指南:MPI与OpenMP加速比曲线绘制及代码分析

并行计算实验报告高分指南:MPI与OpenMP加速比曲线绘制及代码分析

简介:这份郑州大学计算机与人工智能学院并行计算实验报告,由gyb老师指导完成,作者在该课程中取得90分以上成绩,适合正在修读并行计算、高性能计算相关课程的高校学生,以及希望系统理解并行编程实践的学习者参考。报告围…

2026/10/15 0:21:17 阅读更多 →
老鼠走迷宫升级版:C语言数据结构课程设计源码详解与避坑

老鼠走迷宫升级版:C语言数据结构课程设计源码详解与避坑

简介:面向C语言与数据结构课程设计的迷宫游戏升级版项目,主要针对需要完成同类题目的高校学生,提供完整源代码、可执行程序与迷宫地图文件,解决游戏逻辑、路径搜索、地图编辑等课程设计要求。压缩包共5个文件,包含3个t…

2026/10/15 0:20:16 阅读更多 →
z-index不起作用?彻底搞懂CSS层叠上下文与遮挡问题排查

z-index不起作用?彻底搞懂CSS层叠上下文与遮挡问题排查

下午五点多,某后台系统的测试同学甩来一张截图:新增弹窗又被列表表头遮住了,弹窗上那个z-index: 99999明晃晃写着,却像个摆设。我第一反应不是去加更大的数值,而是打开DevTools开始翻它的祖先链——因为这类“z-index设…

2026/10/15 0:20:16 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →