高并发内存池三层架构详解
目录源码高并发内存池_仿tcmalloc: 仿tcmalloc的小项目代码量在1千行左右。该项目缺少线程退出后的内存回收机制还需要配合回调函数使用https://gitee.com/han-ses-first-stick/high-concurrency-memory-poolhttps://gitee.com/han-ses-first-stick/high-concurrency-memory-pool高并发内存池三层架构详解一、三层架构总览二、哈希桶对齐规则三、为什么设计成 Span 模式第一便于空闲内存的回收第二减少锁的粒度实现均衡调度第三利于释放的分散内存合并总结四、Central Cache 锁粒度设计解析五、Page Cache 设计解析六、Thread Cache 上限设计解析七、定长内存池与 new/delete 对比八、性能测试与优化源码高并发内存池_仿tcmalloc: 仿tcmalloc的小项目代码量在1千行左右。该项目缺少线程退出后的内存回收机制还需要配合回调函数使用https://gitee.com/han-ses-first-stick/high-concurrency-memory-poolhttps://gitee.com/han-ses-first-stick/high-concurrency-memory-poolhttps://gitee.com/han-ses-first-stick/high-concurrency-memory-pool摘要本文深入解析仿 tcmalloc 的高并发内存池三层架构设计。文章从 Thread Cache、Central Cache、Page Cache 三层结构入手详细讲解哈希桶对齐规则、Span 模式的设计动机、Central Cache 的桶锁粒度选择、Page Cache 的慢启动算法与整体锁设计以及 Thread Cache 上限的权衡方案。同时对比定长内存池与 new/delete 的差异并通过性能测试展示使用基数树优化哈希表后申请定长内存耗时从 1704ms 降至 819ms验证了优化思路的正确性。高并发内存池三层架构详解1. Thread Cache 256KB 的内存2. Central Cache 管理回收 Thread Cache 并分配内存需要加锁存在多线程竞争内存用的桶锁为了减少锁竞争这里反而采用了增加锁的粒度的方式。3. Page Cache 管理回收 Central Cache 并分配内存一、三层架构总览Thread Cache线程私有高速缓存无锁分配控制内碎片Central Cache全局共享Span 管理页与小对象映射跨线程调度、负载均衡、对象生命周期管理PageCache页级大块内存与 OS 交互处理页回收合并减少系统调用保障缓存局部性分层管理1-3递进Thread Cache 是一个哈希桶结构每一个桶存定长的内存块链表最大时 256KB这也是上面说 Thread Cache 只能申请 256KB 的原因。桶结构比如8byte16byte32byte......256KB每一个内存块的前部分存下一个内存块的地址。二、哈希桶对齐规则central cache也是一个哈希桶结构(映射规则也一样)但是也有不同如下为什么映射规则也是一样的是因为central cache是中央内存池thread cache类似一个一个的自由链表内存还是去central cache中所以拿多少怎么拿规则和thread cache一致就节省了很多代码复杂度。central cache中存的是k字节的链表对应的一个一个的span(连续的大块内存空间可能跨几页)需要k字节就去k字节的value的自由链表中切一个k字节就行了。span是我们自定义的结构体如下三、为什么设计成 Span 模式第一便于空闲内存的回收我们的central cache需要做内存的回收管理这里需要一个前提知识点free(ptr)其中ptr只能是malloc申请出来的起始空间地址不能是其中的偏移量因为libstdc的底层的回收逻辑就是这样的所以我们的central cache为什么要以span为单位就是这个逻辑thread cache来central cache来切内存了把span申请的空间切成了一个一个的小块我们只需要记录切出去的块个数及_usecount当我们回收的时候只需要看_usecount是否为0即可为0我们就还到上一层page cache中自始至终我们的span都是完整的连续空间。第二减少锁的粒度实现均衡调度如果是一个一个的自由链表那么锁的粒度是加在链表上的多个线程同时竞争时就会卡住降低的并发量但是我们使用span,我们的锁粒度就可以加在span上这就相当于一个分片锁了一个锁上的等待时间总体就减小了并发量就上来了。第三利于释放的分散内存合并总结操作系统以页作为内存管理单位连续页空间局部性好CPU 缓存命中率更高。我们从 OS 拿到整页大块内存但不能直接交付用户需要切分成小对象。为提升并发使用 TLS 实现线程独立的 Thread Cache内部按 size 维护哈希自由链表降低内碎片。Thread Cache 只作为本地缓冲区从 Central Cache 批量拿已经切好的对象分配优先走本地实现无锁。Central Cache 是全局中转站哈希桶下挂载多个 SpanSpan 维护页元信息与对象引用计数多 Span 拆分锁粒度实现负载均衡支持内存跨线程流转。当 Span 所有对象归还就将整块页还给 PageCache控制外碎片。PageCache 负责向 OS 申请、回收、合并页面减少系统调用开销。四、Central Cache 锁粒度设计解析1.central cache中的锁为什么不加到Span上而是加到SpanLists上加的是桶锁解答这里考虑的就是锁的数量和代码开发和维护成本的问题如果加到Span上一个SpanLists中的一个Lists链表中要管理n个Span链锁的数量就上来了加在桶上只需要数组大小个锁(我的项目中是定义的208个)细分到Span上就不一样了每一个桶中又有很多个Span乘起来锁有成千上万个了虽然可以增加并发数但是代码量就上来了变得更加复杂代码维护成本也变高了并且在并发时多个线程等待锁拿锁切换线程的开销也变大了。五、Page Cache 设计解析在去Page Cache中拿内存的时候会用到一个类似慢启动的算法在慢开始算法这里如果有频繁申请就代表需求高那我下一次多取一倍直到我自设的上限。这个上限大字节为2小字节为512page cache也是哈希桶映射但是这里有区别central cache和thread cache的映射关系是一致的但是page cache不一样是1-128的递增数字单位是页。并且哈希桶中的自由链表是不会切的我直接给central cache根据你需要的页数来算。其中这里只会去申请128页的固定内存有两个好处第一减少内存外碎片。如果每一页都去申请内存内存申请的大小间隔是不固定的那么前面多个分散的小内存回收了我想申请一块大内存就申请不了了。第二方便内存的回收管理关于page cache的为什么是整体锁而不是桶锁问题第一如果是桶锁那么在分页的时候多个线程就会竞争多把锁线程被唤醒和等待的次数变多线程切换开销变大性能大大降低了第二桶锁还可能导致死锁问题。线程 A 需要 2 页内存B线程需要8页内存A去拿到10页锁去拆成8和2页然后去申请8页锁B拿到了8页锁想去申请10页锁此时线程 A 和线程 B 就会各自持有锁并互相等待对方的锁从而导致死锁问题。在物理内存回收和切分这里都是逻辑切只告诉你起始位置和内存块大小回收的时候根据%8k的余数来确定是哪一页的。在page Cache合并的时候不能使用usecount0这是线程不安全的。为什么能主要是usecount等于0不等于span空闲可合并只能说明没有上层对象引用这个spanusecount并不是用来说明page Cache是否可以合并的充分条件只是来说明central cache中的span分出去的空间是否归还了所以我们就拿一个_isUse来判断是否在使用方便我们在page Cache中做合并判断。在申请内存这里申请256K时就找线程内存池当大于256k小于128*8k时找page cache大于128*8k时找系统堆要。六、Thread Cache 上限设计解析为什么不增加thread cache的上限呢而是向page cache或者堆要呢。解答ThreadCache 设置上限是一个权衡如果把 ThreadCache 上限设置很大每个线程都会预留大量空闲内存多线程场景下内存占用会爆炸内存利用率低如果上限很小频繁向 CentralCache 请求锁竞争变多性能下降。所以采用折中方案给 ThreadCache 设置一个不大的上限。 线程本地内存够用就本地分配本地内存达到上限或者空闲过多时就归还对象给 CentralCacheCentralCache 没有内存再去 PageCachePageCache 没有才向 OS 堆申请。 平衡并发性能和内存利用率。七、定长内存池与 new/delete 对比在我们的内存池中不使用new和delete而是使用我们的定长内存池我们直接使用new和delete是不是更好呢直接将内存还给OS定长内存池满了怎么办呢解答第一个就是脱离原生的malloc和free因为原生malloc自己也会维护一个CRT堆而且很重每一层都会加锁第二就是我们自己设置的定长内存池是无锁的而且是O(1)的八、性能测试与优化使用vs下的性能分析工具发现哈希这里调用次数比较多我们可以对哈希表进行优化使用基数树因为哈希表底层是加了锁的所以我们可以用基数树使用无锁且没有哈希冲突做到O(1)的访问如下申请变长内存的时候对比两者情况如下申请定长内存的时候对比两者情况如下为什么unordered_map会拖慢这么多呢主要是在哈希表底层是加了锁的所以高并发的时候就会造成锁竞争所以下面的优化我们就是直接使用基数树进行一个无锁操作逻辑上的无锁最后再使用的时候一般会打成静态库或者动态库在Linux下我们代码中可以照常写malloc和frer在gcc编译时带上tcmalloc就可以实现替换windows上就使用一个hook(钩子)的操作进行替换。最终使用基数树优化后的申请定长内存的跑分数据如下可以看到我们的内存池从原来的1704ms跑到了819ms说明我们的优化思路是正确的。

相关新闻

3月14日全年进度20%:我的打卡体系与习惯复盘

3月14日全年进度20%:我的打卡体系与习惯复盘

3月14日早上六点半,闹钟还没响,我先伸手摸到手机,把睡眠、晨间体重和运动状态三栏填好,然后翻开打卡记录看了一眼:这是今年连续打卡的第72天。这一天在日历上的标签很多——白色情人节、圆周率日,但对一个长…

2026/10/11 6:19:12 阅读更多 →
从概念到落地:梯田博物馆“漫步体验”多媒体展馆设计复盘

从概念到落地:梯田博物馆“漫步体验”多媒体展馆设计复盘

接到世界梯田博物馆这个项目的时候,我脑子里冒出来的第一个问题不是“展厅怎么做”,而是“怎么把一座山装进一栋建筑里”。梯田不是静态的展品,它是活着的大地艺术,是本地居民日复一日劳作出来的风景。森克思科技团队最终给出的答…

2026/10/11 6:19:12 阅读更多 →
把“沉迷力”变成生产力:从被动刷手机到主动沉浸的实践指南

把“沉迷力”变成生产力:从被动刷手机到主动沉浸的实践指南

“Binge”这个词,大部分人第一反应是“刷剧刷到天亮”“刷短视频停不下来”那种放纵式的沉迷。我自己也经历过:凌晨两点半,眼睛酸到流泪,拇指还在机械地往上划,心里明明知道该睡了,脑子里有个声音说“再看一…

2026/10/11 6:19:12 阅读更多 →

最新新闻

2025年AI IDE实战测评榜:从个人开发到企业部署的完整选型攻略(TaoToken统一API接入篇)

2025年AI IDE实战测评榜:从个人开发到企业部署的完整选型攻略(TaoToken统一API接入篇)

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

2026/10/11 10:24:10 阅读更多 →
付了GPT-5的钱,用的是开源模型?用TaoToken统一Key看清每次调用

付了GPT-5的钱,用的是开源模型?用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/11 10:24:10 阅读更多 →
装完这16个Skills,我的OpenClaw终于会自己查文档了:TaoToken统一Key接入实录

装完这16个Skills,我的OpenClaw终于会自己查文档了: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/11 10:24:10 阅读更多 →
用 Java 5 分钟写一个 MCP Server:基于开源 MCP Java SDK 接入 TaoToken 统一 Key

用 Java 5 分钟写一个 MCP Server:基于开源 MCP Java SDK 接入 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/11 10:24:10 阅读更多 →
免越狱批量控制iPhone:基于Accessibility API的合规自动化方案

免越狱批量控制iPhone:基于Accessibility API的合规自动化方案

1. 为什么“免越狱批量控制iPhone”这件事,过去十年几乎没人真正做成?“不用越狱也能批量控制 iPhone”——这句话放在2024年之前,对绝大多数iOS开发者、自动化测试工程师甚至企业IT管理员来说,都像一句带点讽刺意味的行业黑话。不…

2026/10/11 10:24:10 阅读更多 →
从“impeccable”到工程实践:代码格式化、静态检查与CI流水线

从“impeccable”到工程实践:代码格式化、静态检查与CI流水线

“impeccable”这个词,按读音是 /ɪmˈpɛkəbəl/,意思是“无可挑剔、毫无瑕疵”。我见过不少人把它当成代码注释里的形容词,写“keep the code impeccable”。说实话,第一次看到某公司前端代码仓库的提交规范里,用这…

2026/10/11 10:23:09 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 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/10 5:23:50 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/10 10:38:42 阅读更多 →