3.1.2 内存池
文章目录
- 3.1.2 内存池
- 1. 什么是内存池
- 2. 内存管理
- 1. 定长
- 2. 不定长
- 3. jemalloc
- 4. tcmalloc
3.1.2 内存池
1. 什么是内存池
-
内存池(Memory Pool) 是一种 预先分配 一块大内存,然后按需分配和回收 其中小块内存的技术。它的本质是管理一块连续的大内存区域,避免频繁调用 malloc/free 或 new/delete,提高性能并减少内存碎片。
-
池(线程池、内存池等等)->起一个缓冲作用
-
运行时间长->coredump,内存碎片-> 内存管理组件(内存池)
-
线程池代码都差不多,内存池(指的是虚拟内存管理,就是堆管理)就不一样了
BSS(Block Started by Symbol)段 是程序中的一段内存区域,主要用于存储 未初始化的全局变量 和 静态变量。它的特点是这些变量在程序开始运行时没有被明确地初始化为某个值,而是由操作系统在程序加载时自动填充为零。
2. 内存管理
1. 定长
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MEM_PAGE_SIZE 0x1000 // typedef struct mempool_s {int blocksize;// 16, 32, 64, 128int freecount;//块的个数char *free_ptr;//开始位置char *mem;//整块内存} mempool_t; // sdk --> varchar(32);
//
// 2^n, page_size 4096, block_size: 16, 32, 64, 128//内存池创建
int memp_create(mempool_t *m, int block_size) {if (!m) return -1;m->blocksize = block_size;m->freecount = MEM_PAGE_SIZE / block_size;m->mem = (char *)malloc(MEM_PAGE_SIZE); //分配一个页的内存if (!m->mem) { //NULLreturn -2;}memset(m->mem, 0, MEM_PAGE_SIZE); // 都置0m->free_ptr = m->mem;int i = 0;char *ptr = m->mem;for (i = 0; i < m->freecount; i++) { // 分块,并构建链表*(char **)ptr = ptr + block_size; // 当前块的指针指向下一个块//ptr 是 char*,表示一个内存地址。//(char **)ptr 让 ptr 被解释为指向 char* 的指针(即存储的是 指针)。//*(char **)ptr = ptr + block_size; 将下一个块的地址存入当前块的起始位置,构建 单链表。//可以想象它像 链表的 next 指针,但它存储在内存块的开头ptr = ptr + block_size; // 移动到下一个块} *(char **)ptr = NULL; // 最后一块的指针指向 NULLreturn 0;
}void memp_destory(mempool_t *m) {if (!m) return ;free(m->mem);}void *memp_alloc(mempool_t *m) {if (!m || m->freecount == 0) return NULL;void *ptr = m->free_ptr;m->free_ptr = *(char **)ptr;m->freecount --;return ptr;
}void memp_free(mempool_t *m, void *ptr) {//头插法,把释放的块放到链表的头部*(char **)ptr = m->free_ptr;m->free_ptr = (char *)ptr;m->freecount ++;
}// memp_strcpy
// memp_memcpyint main() {mempool_t m;memp_create(&m, 32);void *p1 = memp_alloc(&m);printf("memp_alloc : %p\n", p1);void *p2 = memp_alloc(&m);printf("memp_alloc : %p\n", p2);void *p3 = memp_alloc(&m);printf("memp_alloc : %p\n", p3);memp_free(&m, p2);
}
- 释放完的块,如何被再次使用
在定长(固定大小)内存池中,释放后的内存块通常通过**空闲链表(Free List)**管理,以便后续分配时可以快速复用 - 4K以上(大块),4K以下(小块)
在内存管理中,4KB 是一个重要的阈值,通常与分页(Page)机制相关
2. 不定长

// 内存块节点(小块内存的管理单元)
typedef struct mp_node_s {unsigned char *last; // 指向当前块的可用内存的起始地址unsigned char *end; // 指向当前块的结束地址struct mp_node_s *next; // 指向下一个内存块的指针
} mp_node_t;// 大块内存节点(存储超出 max 限制的大块内存)
typedef struct mp_large_s {struct mp_large_s *next; // 指向下一个大块void *alloc; // 指向分配的大块内存
} mp_large_t;// 内存池结构体
typedef struct mp_pool_s {size_t max; // 小块内存的最大分配大小struct mp_node_s *head; // 指向链表的头节点(第一个小块内存)struct mp_large_s *large; // 指向大块内存的链表
} mp_pool_t;int mp_create(mp_pool_t *pool, size_t size);
void mp_destory(mp_pool_t *pool);
void *mp_alloc(mp_pool_t *pool, size_t size);
void mp_free(mp_pool_t *pool, void *ptr);// size : 4096
int mp_create(mp_pool_t *pool, size_t size) {if (!pool || size <= 0) return -1; // 检查参数是否合法void *mem = malloc(size); // 分配 size 大小的内存//void*(空指针类型)可以指向任何类型的数据,但它不能直接解引用,需要先转换为具体的指针类型才能解引用。// 初始化第一个小块节点struct mp_node_s *node = (struct mp_node_s *)mem;node->last = (char *)mem + sizeof(struct mp_node_s); // last 指向第一个可用地址node->end = (char *)mem + size; // 记录块的结束位置node->next = NULL; // 目前只有一个块// 初始化内存池pool->head = node;pool->max = size; // 记录最大小块内存大小pool->large = NULL; // 初始时没有大块内存return 0;
}void mp_destory(mp_pool_t *pool) {mp_large_t *l;// 释放所有大块内存for (l = pool->large; l; l = l->next) {if (l->alloc) {free(l->alloc);}}pool->large = NULL;// 释放所有小块内存mp_node_t *node = pool->head;while (node) {mp_node_t *tmp = node->next;free(node);node = tmp;}
}// 分配小块内存
void *mp_alloc(mp_pool_t *pool, size_t size) {if (size > pool->max) {// 如果请求的大小超过 max,直接分配大块return mp_alloc_large(pool, size);}void *ptr = NULL;mp_node_t *node = pool->head;// 遍历小块链表,查找有足够空间的块do {if (node->end - node->last > size) {ptr = node->last;node->last += size; // 移动 last 指针return ptr;}node = node->next;} while (node);// 如果没有可用空间,则创建新的小块return mp_alloc_block(pool, size);
}// 分配大块内存
static void *mp_alloc_large(mp_pool_t *pool, size_t size) {void *ptr = malloc(size);if (ptr == NULL) return NULL;mp_large_t *l;for (l = pool->large; l; l = l->next) {if (l->alloc == NULL) {l->alloc = ptr;return ptr;}}l = mp_alloc(pool, sizeof(mp_large_t));if (l == NULL) {free(ptr);return NULL;}l->alloc = ptr;l->next = pool->large;pool->large = l;return ptr;
}//分配新的小块(扩展内存池)
static void *mp_alloc_block(mp_pool_t *pool, size_t size) {// 创建新内存块void *mem = malloc(pool->max);struct mp_node_s *node = (struct mp_node_s *)mem;node->last = (char *)mem + sizeof(struct mp_node_s);node->end = (char *)mem + pool->max;node->next = NULL;void *ptr = node->last;node->last += size; // 更新 last// 将新块插入到链表末尾mp_node_t *iter = pool->head;while (iter->next != NULL) {iter = iter->next;}iter->next = node;return ptr;
}// 释放指定内存
void mp_free(mp_pool_t *pool, void *ptr) {mp_large_t *l;for (l = pool->large; l; l = l->next) {if (l->alloc == ptr) {free(l->alloc);l->alloc = NULL;return;}}
}int main() {mp_pool_t pool;mp_create(&pool, 4096);void *p1 = mp_alloc(&pool, 100);void *p2 = mp_alloc(&pool, 200);void *p3 = mp_alloc_large(&pool, 5000);mp_free(&pool, p3);mp_destory(&pool);
}
graph TDA[mp_create] -->|初始化内存池| B[mp_node_t]A --> C[mp_large_t]B --> D[mp_alloc_block]B --> E[mp_alloc_large]D -->|分配内存块| F[mp_alloc]E -->|分配大块内存| FF --> G[mp_free]G --> H[mp_destory]F --> I[mp_alloc_block] C --> J[mp_node_t]subgraph A [创建内存池]A1[创建内存池]A2[初始化小块内存]A3[初始化大块内存]endsubgraph D [分配内存块]D1[检查是否有足够空间]D2[创建新内存块并添加到链表]endsubgraph F [分配内存]F1[根据大小决定分配]F2[调用对应的分配函数]endsubgraph G [释放内存]G1[释放小块内存]G2[释放大块内存]endsubgraph H [销毁内存池]H1[释放所有内存块]endA1 --> A2A2 --> A3D1 --> D2F1 --> F2G1 --> G2H1 --> H
3. jemalloc
- 适用于高并发、多线程应用(如数据库、缓存系统)
- 低碎片率,更适合长期运行的服务
- 支持
madvise()释放未使用内存,降低 RSS 占用
4. tcmalloc
- 超快的小对象分配,减少
malloc/free开销 - 适用于 Google 生态,如 gRPC、Go 语言 runtime
- 批量释放机制,但不会主动归还系统内存
| 特性 | jemalloc | tcmalloc |
|---|---|---|
| 线程缓存 | ✅ 线程私有 tcache,减少锁竞争 | ✅ 线程本地缓存(Thread-local Cache) |
| 碎片率 | ✅ 更低,适合长期运行的系统 | ❌ 相对较高 |
| 小对象分配 | ✅ 使用 Bins 机制优化 | ✅ 高效,适用于小对象高频申请 |
| 大对象管理 | ✅ Extents 细粒度管理 | ✅ mmap 直接分配,减少锁争用 |
| 释放策略 | ✅ madvise() 归还内存 | ❌ 批量回收,但不主动归还系统 |
| 适用场景 | ✅ Redis、MySQL、MongoDB | ✅ Google 生态、Go runtime、gRPC |
相关文章:
3.1.2 内存池
文章目录 3.1.2 内存池1. 什么是内存池2. 内存管理1. 定长2. 不定长3. jemalloc4. tcmalloc 3.1.2 内存池 1. 什么是内存池 内存池(Memory Pool) 是一种 预先分配 一块大内存,然后按需分配和回收 其中小块内存的技术。它的本质是管理一块连续…...
基于SpringBoot + Vue 的餐厅点餐管理系统
SpringBootVue餐厅点餐管理系统 技术框架 后端:springboot mybatisPlus前端:Vue2 elementUI数据库:mysql项目构建工具:maven 数据库表 14张 角色及功能 管理员:登录、用户管理、餐桌信息管理、菜品类型管理、菜…...
Android开发BasePagerAdapter
Android开发BasePagerAdapter 有个基类的PagerAdapter 方便很多 public class BasePagerAdapter extends FragmentPagerAdapter {private static final String TAG "FragmentPagerAdapter";private static final boolean DEBUG false;private final FragmentMana…...
70. Linux驱动开发与裸机开发区别,字符设备驱动开发
一、裸机驱动开发回顾 1、底层,跟寄存器打交道,有些MCU提供了库。 二、Linux驱动开发思维 1、Linux下驱动开发直接操作寄存器不现实。 2、根据Linux下的各种驱动框架进行开发。一定要满足框架,也就是Linux下各种驱动框架的掌握。 3、驱动最…...
【博客节选】再谈Unity 的 root motion
节选自 【Unity实战笔记】第二十三 root motion变更方向攻击 (OnStateMove rootmotion rigidbody 使用的一些问题) 小伙伴们应该对root motion非常困惑,包括那个bake into pose。 当xz bake into pose后,角色攻击动画与父节点产…...
26考研——栈、队列和数组_栈(3)
408答疑 文章目录 一、栈1、栈(Stack)的概念和特点定义术语操作特性示例直观理解栈的基本操作初始化栈判断栈是否为空入栈操作出栈操作读取栈顶元素销毁栈 栈的数学性质 2、栈的顺序存储结构顺序栈的定义栈顶指针初始化注意事项 共享栈共享栈的操作共享栈…...
“十五五”时期航空弹药发展环境分析
1.“十五五”时期航空弹药发展环境分析 (标题:小二号宋体居中) 一、建言背景介绍 (一级标题:黑体三号,首行空两格) 航空弹药作为现代战争的核心装备,其发展水平直接关乎…...
桥接模式的优点和典型实现
桥接模式的优点 桥接模式通过将抽象部分与实现部分分离,使得它们可以独立变化,从而提高系统的灵活性和可扩展性。以下是桥接模式的主要优点: 分离抽象和实现: 桥接模式将抽象部分和实现部分分离,使得两者可以独立变化…...
Kotlin 协程官方文档知识汇总(一)
1、协程基础 Kotlin 是一门仅在标准库中提供最基本底层 API 以便其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async 与 await 在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的挂起函数概念为异步操作提…...
删除字符串邻近的重复项
1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode) 对于字符串中字符的匹配或者删除等问题,通常会用到栈这个数据结构,要保持这样一个思路。 对于这道题,可以遍历字符串,用栈存储,一旦遇到…...
基于 mxgraph 实现流程图
mxgraph 可以实现复杂的流程图绘制。mxGraph里的Graph指的是图论(Graph Theory)里的图而不是柱状图、饼图和甘特图等图(chart),因此想找这些图的读者可以结束阅读了。 作为图论的图,它包含点和边,如下图所示。 交通图 横道图 架构图 mxGrap…...
动态路由机制MoE专家库架构在多医疗AI专家协同会诊中的应用探析
随着医疗人工智能技术的飞速进步,AI在医学领域的应用日益增多,尤其是在复杂疾病的诊断和治疗中,AI技术的应用带来了巨大的潜力。特别是动态路由机制混合专家(Mixture of Experts,MoE)架构,因其灵活、高效的特点,正逐渐成为实现多AI专家协同会诊的关键技术。通过将多个不…...
双工通信:WebSocket服务
(一)WebSocket概述 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输 注意;Websocket也只能由客户端先握…...
洪水灌溉算法 + 总结
文章目录 floodfill算法图像渲染题解代码 岛屿数量题解代码 岛屿的最大面积题解代码 被围绕的区域题解代码 太平洋大西洋水流问题题解代码 扫雷游戏题解代码 衣橱整理题解代码 总结 floodfill算法 1. 寻找相同性质的联通块,可以使用dfs或者bfs解决,比如…...
docker中间件部署
1.docker安装 # 1.卸载旧版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine# 2.需要的安装包 yum install -y yum-utils# 3.设置镜像的仓库 # 3.1.默认是国外的&#x…...
LangChain4j(1):初识LangChain4j
1 什么是LangChain和LangChain4j LangChain是一个大模型的开发框架,使用LangChain框架,程序员可以更好的利用大模型的能力,大大提高编程效率。如果你是一个lava程序员,那么对LangChain最简单直观的理解就是,LangChain…...
基于 Swoole 的高性能 RPC 解决方案
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
Photoshop 2025安装包下载及Photoshop 2025详细图文安装教程
文章目录 前言一、Photoshop 2025安装包下载二、Photoshop 2025安装教程1.解压安装包2.运行程序3.修改安装路径4.设安装目录5.开始安装6.等安装完成7.关闭安装向导8.启动软件9.安装完成 前言 无论你是专业设计师,还是初涉图像处理的小白,Photoshop 2025…...
CPU跑大模型怎么加速?
一、概念 近几年,大模型的规模越做越大。普通码农没几张显卡几乎都跑不动动辄几百B的模型了。当然,随着SLM进一步发展,移动端、PC端部署SLM变得轻松了起来。即便只有CPU也能带得起3B以内的SLM,只不过推理速度比较感人。因此&#…...
PostgreSQL详解
第一章:环境部署与基础操作 1.1 多平台安装详解 Windows环境 图形化安装 下载EnterpriseDB安装包(含pgAdmin) 关键配置项说明: # postgresql.conf优化项 max_connections 200 shared_buffers 4GB work_mem 32MB 服务管理命…...
SQL Server安装程序无法启动:系统兼容性检查失败
问题现象: 运行 SQL Server 2022 安装程序时,提示 “硬件或软件不满足最低要求”,安装向导直接退出或无法继续。 快速诊断 操作系统版本检查: # 查看 Windows 版本(需 20H2 或更高) winver 支持的系统&…...
期权合约作废的话,权利金和保证金会退还么?
在期权交易中,权利金是否可以退回,主要取决于期权的交易情况和合约条款。 期权作废的三种情形 一般来说期权作废一共有三种情况,分别是到期没有行权、主动放弃或者是标的退市了。 第一种是到期未行权,一般来说值得都是虚值期权&…...
MIPI计算ECC和CRC工具介绍
一、MIPI简介 MIPI联盟,即移动产业处理器接口(Mobile Industry Processor Interface 简称MIPI)联盟。MIPI(移动产业处理器接口)是MIPI联盟发起的为移动应用处理器制定的开放标准和一个规范。MIPI官网https://mipi.org/…...
医院管理系统(源码)分享
「医院管理系统(源码) 源码: https://pan.quark.cn/s/b6e21488fce3 第1章 绪论 1.1 项目背景 随着计算机科学的迅猛发展和互联网技术的不断推进,人们的生活方式发生了巨大的变化,同时也推动了整个软件产业的发展。把…...
使用Geotools从DEM数据中读取指定位置的高程实战
目录 前言 一、GridCoverage2D对象介绍 1、GridCoverage2D的属性 2、GridCoverage2D核心方法 3、GridCoverage2D中的高级操作 二、指定位置的高程获取 1、存储原理 2、相关属性的获取 3、获取高程的方法 三、总结 前言 在地理信息科学领域,高程数据是至关重…...
uniapp 在app上 字体如何不跟着系统字体大小变
在UniApp开发中,默认情况下App的字体可能会跟随系统字体设置而变化。如果你希望保持固定的字体样式,不随系统字体设置改变,可以采用以下几种方法: 方法一:全局CSS设置 在App.vue的样式中添加以下CSS: /*…...
RAG优化:python从零实现GraphRag 一场文档与知识的“恋爱”之旅
嘿,亲爱的算法工程师们,准备好迎接一场文档与知识的“恋爱”之旅了吗?今天我们要介绍的 Graph RAG,就像是一位“红娘”,帮助文档和知识在图的世界里找到彼此,擦出智慧的火花! 文章目录 为什么需要 Graph RAG?Graph RAG 的“恋爱秘籍”准备好了吗?让我们开始吧!环境设…...
STM32F103_LL库+寄存器学习笔记05 - GPIO输入模式,捕获上升沿进入中断回调
导言 GPIO设置输入模式后,一般会用轮询的方式去查看GPIO的电平状态。比如,最常用的案例是用于检测按钮的当前状态(是按下还是没按下)。中断的使用一般用于计算脉冲的频率与计算脉冲的数量。 项目地址:https://github.…...
如何为你的github开源项目选择合适的开源协议?
如何为你的github开源项目选择合适的开源协议? 导言 在github开源世界中,选择一个合适的开源协议是至关重要的。它不仅定义了他人如何使用你的代码,还决定了你的项目能否被广泛接受和传播,还能避免侵权问题。 然而,面…...
【深度破解】爬虫反反爬核心技术实践:验证码识别与指纹伪装
一、反爬技术体系全景图 现代Web应用的常见反爬手段: mermaid: graph TDA[反爬体系] --> B[行为特征检测]A --> C[验证码体系]A --> D[指纹追踪]B --> B1[请求频率]B --> B2[鼠标轨迹]B --> B3[页面停留时间]C --> C1[图形验证码…...
