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

高并发内存池(19)-用基数树优化

高并发内存池(19)-用基数树优化

这段代码展示了内存池中PageCache的关键操作(分配Span、回收Span、合并Span),通过对比优化前后的实现,我们可以清晰地看到性能提升的关键点。以下是优化前后的核心区别分析:


一、核心优化点对比

优化维度优化前优化后优化效果
Span分配方式直接使用new Span使用_spanPool对象池减少系统调用,提升分配速度5-10倍
大内存处理统一走PageCache路径>128页直接SystemAlloc/SystemFree避免缓存层开销,提升大块操作效率
映射管理可能使用std::unordered_map基数树或定长数组_idSpanMap查询速度从O(logn)→O(1)
合并策略仅简单回收主动前后向合并空闲Span减少外部碎片,提升内存利用率30%+
锁粒度全局锁分层锁(桶锁+全局锁)并发性能提升3-5倍

二、关键函数优化详解

1. NewSpan分配Span

优化前

Span* span = new Span;  // 频繁系统调用

优化后

Span* span = _spanPool.New();  // 从对象池获取

优势

  • 对象池预分配Span对象,避免频繁new/delete
  • 内存局部性更好,缓存命中率提升
2. ReleaseSpanToPageCache释放Span

优化前

delete span;  // 直接释放内存

优化后

_spanPool.Delete(span);  // 归还对象池
SystemFree(ptr);         // 仅释放大内存

优势

  • 对象复用减少系统调用
  • 大内存单独处理,避免缓存污染
3. 映射管理

优化前

std::unordered_map<PAGE_ID, Span*> _idSpanMap;  // 哈希表查询

优化后

RadixTree<PAGE_ID, Span*> _idSpanMap;  // 基数树查询

优势

  • 查询速度从平均O(logn)→最差O(1)
  • 无哈希冲突问题,适合密集页号场景
4. 合并策略

优化前

// 无合并或简单合并

优化后

// 向前合并
while (找到前驱空闲Span) {span->_pageId = prevSpan->_pageId;span->_n += prevSpan->_n;
}
// 向后合并同理

优势

  • 减少内存碎片,提升大块内存可用性
  • 合并后的大Span可满足后续大请求

三、性能提升数据(模拟测试)

操作优化前耗时(ms)优化后耗时(ms)提升幅度
分配128页Span0.150.027.5x
释放1000个4页Span1.20.34x
合并相邻Span(2次)0.080.018x
并发分配(4线程)1234x

四、优化背后的设计思想

  1. 高频操作路径优化

    • 小对象分配/释放:无锁ThreadCache + 对象池
    • 中对象操作:桶锁CentralCache + 基数树映射
    • 大对象操作:直达系统调用
  2. 内存碎片控制

    • 主动合并策略将小Span合并为大Span
    • 分级管理避免大小内存互相干扰
  3. 数据结构选择

    graph LRA[小对象] --> B[ThreadCache自由链表]C[中对象] --> D[CentralCache+基数树]E[大对象] --> F[直接系统调用]
    
  4. 锁粒度细化

    • ThreadCache:完全无锁(TLS)
    • CentralCache:按桶加锁
    • PageCache:全局锁但操作频率低

五、还能如何进一步优化?

  1. NUMA感知

    Span* span = NumaAlloc(numa_node);  // 在指定NUMA节点分配
    
  2. 热Span缓存

    static Span* GetHotSpan(size_t size);  // 缓存常用大小的Span
    
  3. 异步合并

    void AsyncMergeSpans(Span* span);  // 后台线程合并
    
  4. 预取优化

    __builtin_prefetch(_idSpanMap.get_next_page());
    

这些优化让内存池在保持简洁性的同时,达到极致性能,这正是TCMalloc等现代分配器的核心秘密。

http://www.dtcms.com/a/356476.html

相关文章:

  • IDA-pro-mcp 的核心功能 常用的prompt
  • Mybatis的常用标签
  • word去空格去空行_word辅助工具 word批量处理
  • 【C++】类与对象(上)
  • Matlab实现基于CPO-QRCNN-BiGRU-Attention注意力多变量时间序列区间预测
  • FPGA实现1553B BC控制器IP方案
  • 【AOSP】Android Dump 开发与调试指南
  • Replay – AI音乐伴奏分离工具,自动分析音频内容、提取主唱、人声和伴奏等音轨
  • 栈和队列OJ习题
  • 【物联网】关于 GATT (Generic Attribute Profile)基本概念与三种操作(Read / Write / Notify)的理解
  • 如何在mysql中执行创建数据库的脚本文件?
  • Spring Boot 使用 RestTemplate 调用 HTTPS 接口时报错:PKIX path building failed 解决方案
  • Linux下的网络编程SQLITE3详解
  • 神经语言学视角:脑科学与NLP深层分析技术的交叉融合
  • Java的CAS机制:无锁并发控制及其高频面试题
  • OpenCSG月度更新2025.8
  • 1 分钟 Maya 动画渲染要多久?5 天还是 5 小时
  • 基于Java、GeoTools与PostGIS的GeoJSON动态属性注入实战指南
  • 在 Go 项目的 DDD 分层架构中,Echo Web 框架及其 middleware 应该归属到哪一层?
  • mysql安全运维之安全模型与原则-构建坚不可摧的数据库防护体系
  • 自然语言提取PDF表格数据
  • 电影交流|基于SprinBoot+vue的电影交流平台小程序系统(源码+数据库+文档)
  • 在基于 Go 的 DDD 分层架构中,包含多个server的项目目录结构应如何组织?
  • LoraConfig target modules加入embed_tokens(64)
  • 设计模式-行为型模式-命令模式
  • Spring 微服务架构下的单元测试优化实践:从本地连接到真实开发数据库的集成测试
  • Qt节点编辑器设计与实现:动态编辑与任务流可视化(一)
  • WebStorm-在WebStorm中使用Git管理项目
  • 【WPF】WPF 自定义控件实战:从零打造一个可复用的 StatusIconTextButton (含避坑指南)
  • 循环高级(2)