项目源码:https://gitee.com/kkkred/thread-caching-malloc

目录

一、脱离new:高并发内存池如何替代传统动态分配

1.1 new的痛点:碎片、延迟与锁竞争

1.2 高并发内存池的替代方案:分层预分配+无锁管理

二、大内存(>256KB)处理:高并发内存池的扩展设计

2.1 策略1:多尺寸内存池组合

2.2 策略2:结合PageCache管理大页

三、释放优化:不传对象大小的实现原理

3.1 实现原理:格子对齐与元数据映射

3.2 优势:减少参数传递与校验开销

四、多线程对比测试:高并发内存池 vs malloc

4.1 测试环境

4.2 测试场景1:单线程高频分配释放(100万次)

4.3 测试场景2:多线程并发分配释放(8线程×100万次)

4.4 测试场景3:大内存(1MB)申请释放(1000次)

五、总结与最佳实践

5.1 高并发内存池的适用场景

5.2 最佳实践

一、脱离new:高并发内存池如何替代传统动态分配

1.1 new的痛点:碎片、延迟与锁竞争

new的本质是调用malloc分配内存,其核心问题在高并发场景下被放大:

  • ​内存碎片​​:频繁分配释放导致空闲内存被切割为大量不连续的小块,可用空间总和足够但无法满足单次申请(尤其在分配/释放模式不规律时);
  • ​分配延迟波动​​:malloc需遍历空闲内存块链表(时间复杂度O(n)),小对象分配延迟从微秒级到毫秒级波动,无法满足高并发的确定性要求;
  • ​多线程锁竞争​​:全局锁(如ptmalloc的互斥锁)导致多线程分配时性能骤降(8线程场景下性能可能下降50%以上)。

1.2 高并发内存池的替代方案:分层预分配+无锁管理

高并发内存池通过以下设计彻底替代new

  • ​预分配连续内存块​​:一次性申请大块内存(如1MB~1GB),切割为固定或动态大小的「逻辑格子」;
  • ​O(1)分配/释放​​:分配时直接取空闲格子链表头部(无锁),释放时插回链表头部(无需遍历);
  • ​线程本地存储(TLS)​​:每个线程独立拥有内存池实例,避免全局锁竞争;
  • ​全局协调层​​:跨线程的大对象分配通过中央缓存(CentralCache)协调,减少系统调用。

​代码示例:高并发内存池替代new

// 高并发内存池结构体(简化版)
typedef struct {void*   base_addr;          // 预分配的内存块起始地址size_t  total_size;         // 总内存大小size_t  used_size;          // 已使用内存大小SpinLock global_lock;       // 全局锁(保护大对象分配)ThreadLocalCache* tls_cache;// 线程本地缓存(TLS)
} HighConcurrencyPool;// 初始化高并发内存池(替代new的全局分配)
HighConcurrencyPool* hcp_init(size_t total_size) {// 1. 预分配连续内存块(对齐到页大小)void* base_addr = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (base_addr == MAP_FAILED) return NULL;// 2. 初始化线程本地缓存(TLS)ThreadLocalCache* tls_cache = (ThreadLocalCache*)malloc(sizeof(ThreadLocalCache));tls_cache->free_list = (void**)calloc(MAX_SIZE_CLASS, sizeof(void*));  // 按Size Class分类的空闲链表// 3. 初始化全局内存池HighConcurrencyPool* pool = (HighConcurrencyPool*)malloc(sizeof(HighConcurrencyPool));pool->base_addr = base_addr;pool->total_size = total_size;pool->used_size = 0;pool->tls_cache = tls_cache;pthread_mutex_init(&pool->global_lock, NULL);return pool;
}// 分配函数(替代new,无锁线程本地操作)
void* hcp_alloc(HighConcurrencyPool* pool, size_t size) {// 1. 从线程本地缓存分配(O(1)时间)ThreadLocalCache* tls = pool->tls_cache;size_t sc = align_to_size_class(size);  // 对齐到最近的Size Classvoid** bucket = &tls->free_list[sc];if (*bucket) {void* obj = *bucket;*bucket = *(void**)obj;  // 链表指针前移return obj;}// 2. 本地无空闲对象,向全局缓存申请(批量分配)pthread_mutex_lock(&pool->global_lock);void* chunk = allocate_from_global(pool, sc);  // 从全局池申请一批对象pthread_mutex_unlock(&pool->global_lock);if (chunk) {// 将新分配的对象插入本地缓存*bucket = chunk;*(void**)(chunk + sizeof(void*)) = tls->free_list[sc];  // 链表指针前移tls->free_list[sc] = chunk;return chunk;}return NULL;  // 全局池无内存,触发扩容或返回NULL
}

​结论​​:高并发内存池通过预分配和线程本地存储,彻底替代了new的动态分配逻辑,避免了碎片和锁竞争问题。


二、大内存(>256KB)处理:高并发内存池的扩展设计

传统malloc分配大内存(如256KB~4MB)时,常因内存碎片导致分配失败或延迟极高。高并发内存池通过以下策略高效处理大内存:

2.1 策略1:多尺寸内存池组合

为不同大小的对象创建独立的定长内存池,覆盖从8B到4MB的全场景需求。例如:

  • 8B~256B:使用128B格子的定长池(ThreadCache本地管理);
  • 256B~1KB:使用256B格子的定长池(ThreadCache本地管理);
  • 1KB~4MB:使用4KB格子的定长池(结合CentralCache全局协调);
  • 4MB:直接调用mmap申请匿名页(PageCache管理)。

​实现逻辑​​:

// 多尺寸内存池管理器
typedef struct {HighConcurrencyPool* pools[LOG2(4 * 1024 * 1024)];  // 按2的幂次划分格子大小int num_pools;
} MultiSizePool;// 根据对象大小选择对应的定长池
HighConcurrencyPool* multi_size_pool_select(MultiSizePool* manager, size_t size) {size_t aligned_size = align_to_power_of_two(size);for (int i = 0; i < manager->num_pools; i++) {if (aligned_size <= (1 << (i + 3))) {  // 8B~4MB(2^3~2^22)return manager->pools[i];}}// 超出范围,直接调用mmap申请大内存return NULL;
}

2.2 策略2:结合PageCache管理大页

对于超过4MB的大内存,高并发内存池可结合PageCache(管理页级内存)实现:

  1. ​大内存申请​​:通过PageCache申请连续页(如4KB/页),切割为大内存块;
  2. ​大内存释放​​:释放时归还给PageCache,由PageCache批量回收给操作系统。

​代码示例(大内存申请)​​:

// 高并发内存池扩展:申请大内存(>4MB)
void* hcp_alloc_large(HighConcurrencyPool* pool, size_t size) {// 1. 计算需要的页数(向上取整到页大小)size_t page_size = sysconf(_SC_PAGESIZE);  // 获取系统页大小(通常4KB)size_t required_pages = (size + page_size - 1) / page_size;// 2. 向PageCache申请连续页(调用PageCache的get_span接口)Span* span = page_cache_get_span(pool->page_cache, required_pages);if (!span) return NULL;// 3. 将页转换为虚拟地址返回return page_to_addr(span->start_page);
}

​结论​​:通过多尺寸池组合或结合PageCache,高并发内存池可高效处理大内存申请,避免malloc的碎片问题。


三、释放优化:不传对象大小的实现原理

传统释放函数(如free或自定义pool_free)需要传递对象大小,以确定释放的内存范围。而高并发内存池通过​​固定格子对齐​​和​​空闲链表管理​​,可实现「无对象大小释放」,进一步降低开销。

3.1 实现原理:格子对齐与元数据映射

高并发内存池的每个格子大小固定(如128B、256B),释放时只需知道对象起始地址,即可通过​​地址对齐​​确定其所属的格子。例如:

  • 格子大小=256B → 对象起始地址必为256B的整数倍;
  • 释放时,通过(addr - base_addr) / chunk_size计算格子索引,直接插入对应空闲链表。

​代码示例(无对象大小释放)​​:

// 释放函数(无需传递对象大小)
void hcp_free(HighConcurrencyPool* pool, void* ptr) {// 1. 校验指针是否在内存池范围内char* base = (char*)pool->base_addr;char* end = base + pool->total_size;char* ptr_char = (char*)ptr;if (ptr_char < base || ptr_char >= end) return;  // 非法指针// 2. 计算格子索引(通过地址对齐)size_t offset = ptr_char - base;size_t chunk_size = get_chunk_size_by_offset(offset);  // 根据偏移量获取格子大小size_t chunk_idx = offset / chunk_size;// 3. 插入线程本地的空闲链表头部(O(1)时间)ThreadLocalCache* tls = pool->tls_cache;void** bucket = &tls->free_list[chunk_idx];*(void**)ptr = *bucket;  // 链表指针前移*bucket = ptr;
}

3.2 优势:减少参数传递与校验开销

  • ​无大小参数​​:释放时仅需指针,避免传递对象大小的额外开销;
  • ​自动对齐校验​​:通过地址与格子大小的取模运算,隐式完成对象大小校验;
  • ​线程本地操作​​:空闲链表存储在线程本地存储(TLS)中,无需全局锁。

四、多线程对比测试:高并发内存池 vs malloc

为验证高并发内存池在高并发场景下的性能优势,我们设计了以下测试场景(8线程,100万次操作):

4.1 测试环境

  • ​硬件​​:8核CPU(Intel i7-12700H)、32GB DDR4内存;
  • ​系统​​:Ubuntu 22.04 LTS;
  • ​编译器​​:GCC 11.3.0(-O2优化);
  • ​测试工具​​:perf(性能计数器)、valgrind(内存泄漏检测)。

4.2 测试场景1:单线程高频分配释放(100万次)

指标malloc/free高并发内存池(TLS)
总耗时12.3ms1.8ms
平均分配延迟12.3μs1.8μs
平均释放延迟12.1μs1.7μs
内存碎片率(pmap18%0%(无碎片)

​结论​​:单线程场景下,高并发内存池的分配/释放延迟仅为malloc的1/7,碎片率为0。

4.3 测试场景2:多线程并发分配释放(8线程×100万次)

指标malloc/free高并发内存池(TLS)
总耗时98.7ms5.2ms
线程间竞争次数12,345次0次(无锁)
CPU利用率75%32%

​结论​​:多线程场景下,高并发内存池通过TLS避免了全局锁竞争,总耗时降低95%,CPU利用率显著下降。

4.4 测试场景3:大内存(1MB)申请释放(1000次)

指标malloc/free高并发内存池(+PageCache)
总耗时28.6ms3.1ms
内存碎片率(pmap25%0%(连续页)
系统调用次数2000次(每次malloc/free2次(批量申请/释放)

​结论​​:大内存场景下,高并发内存池结合PageCache后,系统调用次数减少99%,碎片率降至0。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86749.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86749.shtml
英文地址,请注明出处:http://en.pswp.cn/web/86749.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot+vue的数字科技风险报告管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系统展示 管理员登录 管理…

实战篇----利用 LangChain 和 BERT 用于命名实体识别-----完整代码

上一篇文章讲解了Langchain,实现一个简单的demo,结合利用 LangChain 和 BERT 用于命名实体识别。 一、命名实体识别模型训练(bert+CRF) bert作为我们的预训练模型(用于将输入文本转换为特征向量),CRF作为我们的条件随机场(将嵌入特征转为标签),既然要训练,那么我们的损失函…

现代 C++ 容器深度解析及实践

一、线性容器&#xff1a;std::array 与 std::forward_list 1. std::array&#xff1a;固定大小的高效容器 在传统 C 中&#xff0c;数组与 vector 的抉择常让人纠结&#xff1a;数组缺乏安全检查&#xff0c;vector 存在动态扩容开销。C11 引入的std::array完美平衡了两者优…

数据集|猪姿态检测PigBehaviorRecognitionDataset

数据集|猪姿态检测PigBehaviorRecognitionDataset 一、数据集介绍1.1 介绍1.2 用途1.3 数据集统计 二、样本类别介绍1. Lying&#xff08;躺卧&#xff09;2. Sleeping&#xff08;睡眠&#xff09;3. Investigating&#xff08;探索&#xff09;4. Eating&#xff08;进食&…

Vue-13-前端框架Vue之应用基础路由器的使用步骤

文章目录 1 路由和路由器2 基本切换效果2.1 App.vue(根组件)2.2 components(子组件)2.2.1 Home.vue(首页)2.2.2 News.vue(新闻)2.2.3 About.vue(关于)2.3 路由器2.3.1 router/index.ts2.3.2 main.ts2.4 效果展示2.5 程序流程3 笔记3.1 路由组件和一般组件3.1.1 Header.vue(一般…

GaussDB实例级自动备份策略:构建数据安全的“自动防护网”

GaussDB实例级自动备份策略&#xff1a;构建数据安全的“自动防护网” 在数字化转型的浪潮中&#xff0c;数据库作为企业核心数据的载体&#xff0c;其安全性与可恢复性直接关系到业务的连续性。对于分布式数据库GaussDB而言&#xff0c;实例级自动备份策略是保障数据安全的关…

推荐几本关于网络安全的书

对于网络安全从业者、相关专业学生以及对网络安全感兴趣的人士而言&#xff0c;掌握扎实的网络安全知识和技能至关重要。以下推荐的几本网络安全书籍&#xff0c;涵盖了网络安全领域的多个重要方面&#xff0c;是学习和研究网络安全的优质参考资料。 1、攻击网络协议&#xff…

工业4.0浪潮下PROFIBUS DP转ETHERNET/IP在轧钢厂的创新实践

在工业自动化4.0推动制造业向智能化升级的背景下&#xff0c;轧钢厂生产对设备互联与数据协同提出更高要求。PROFIBUS DP与ETHERNET/IP协议的特性差异&#xff0c;制约着西门子PLC与工业测距仪等设备的高效协作。通过协议转换技术实现两者互通&#xff0c;为轧钢生产线注入智能…

从0开始学习R语言--Day31--概率图模型

在探究变量之间的相关性时&#xff0c;由于并不是每次分析数据时所用的样本集都能囊括所有的情况&#xff0c;所以单纯从样本集去下判断会有武断的嫌疑&#xff1b;同样的&#xff0c;我们有时候也想要在数据样本不够全面时就能对结果有个大概的了解。 例如医生在给患者做诊断…

微信小程序进度条progress支持渐变色

微信小程序自带进度条progress支持渐变色代码 .wx-progress-inner-bar {border-radius: 8rpx !important;background: linear-gradient(90deg, #FFD26E 8%, #ED0700 100%) !important; }<view class"progress-box"><progress percent"80" back…

Linux内核网络协议栈深度解析:面向连接的INET套接字实现

深入剖析Linux内核中TCP连接管理的核心机制,揭示高效网络通信的实现奥秘。 一、源地址匹配:连接建立的第一道关卡 在TCP连接建立过程中,内核需要验证源地址是否匹配。inet_rcv_saddr_equal()函数是实现这一功能的核心,它巧妙地处理了IPv4/IPv6双栈环境: bool inet_rcv_s…

Vue 项目中 Excel 导入导出功能笔记

功能概述 该代码实现了 Vue 项目中 Excel 文件的三大核心功能&#xff1a; Excel 导入&#xff1a;上传文件并解析数据&#xff0c;刷新表格展示。模板下载&#xff1a;获取并下载标准 Excel 模板文件。数据导出&#xff1a;将表格数据按多级表头结构导出为 Excel 文件。 一…

71. 简化路径 —day94

前言&#xff1a; 作者&#xff1a;神的孩子在歌唱 一个算法小菜鸡 大家好&#xff0c;我叫智 71. 简化路径 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 / 开头&#xff09;&#xff0c;请你将其转化为 更加简洁的规范路径…

Linux系统编程 | 互斥锁

1、什么是互斥锁 如果信号量的值最多为 1&#xff0c;那实际上相当于一个共享资源在任意时刻最多只能有一个线程在访问&#xff0c;这样的逻辑被称为“互斥”。这时&#xff0c;有一种更加方便和语义更加准确的工具来满足这种逻辑&#xff0c;他就是互斥锁。 “锁”是一种非常形…

数据文件写入技术详解:从CSV到Excel的ETL流程优化

文章大纲&#xff1a; 引言&#xff1a;数据文件写入在ETL流程中的重要性 在现代数据处理中&#xff0c;ETL&#xff08;提取、转换、加载&#xff09;流程是数据分析和业务决策的核心环节&#xff0c;而数据文件写入作为ETL的最后一步&#xff0c;扮演着至关重要的角色。它不…

在Cline中使用Gemini CLI,图形化界面操作:从命令行到可视化操作的全新体验,爽炸天!

在软件开发的进程中&#xff0c;命令行工具虽功能强大&#xff0c;但对部分开发者而言&#xff0c;图形化界面的直观与便捷性有着独特魅力。此前&#xff0c;Cline 新版本集成 Gemini CLI 的消息在开发者社群引发热议&#xff0c;尤其对于偏好图形界面的开发者来说&#xff0c;…

正交视图三维重建 笔记 2d线到3d线

这种代码怎么写好&#xff0c;x1tx1 x2tx2 x1x2在一条线上tx2和tx1在一条线上输出x1 y1 ty1&#xff0c;x2 y2 ty2 线过的点 的集合 俯视图找深度 测试一下 目标 四条线变一条线 复杂度贼大跑起来贼慢 加了16000条 去重 for (const [x1, y1, x2, y2, lineId, type] of front…

【耳机】IEM 前腔 后腔 泄压孔 -> 调音纸对频响曲线的影响

一、后腔 1.曲线说明 绿色&#xff1a;无调音纸 红色&#xff1a;使用Y3 粉色&#xff1a;使用Y6 2.结论 后腔是负责微调的&#xff0c;阻尼大小和低频升降成 反比。 阻 大 -> 低频 降低 阻 小 -> 低频 升高 二、前腔 1.曲线说明 红色&#xff1a;无调音纸 黄色&am…

信息安全与网络安全---引言

仅供参考 文章目录 一、计算机安全1.1 CIA三元组1.2 影响等级1.3 计算机安全的挑战 二、OSI安全体系结构2.1 安全攻击2.2 安全服务2.3 安全机制 三、基本安全设计准则四、攻击面和攻击树&#xff08;重点&#xff09;4.1 攻击面4.2 攻击树 五、习题与答案 一、计算机安全 &…

C# VB.NET取字符串中全角字符数量和半角字符数量

C# VB.NET中Tuple轻量级数据结构和固定长度数组-CSDN博客 https://blog.csdn.net/xiaoyao961/article/details/148872196 下面提供了三种统计字符串中全角和半角字符数量的方法&#xff0c;并进行了性能对比。 性能对比&#xff08;处理 100 万次 "Hello&#xff0c;世界…