3.1.2 内存池

news2025/3/29 14:03:59

文章目录

  • 3.1.2 内存池
    • 1. 什么是内存池
    • 2. 内存管理
      • 1. 定长
      • 2. 不定长
      • 3. jemalloc
      • 4. tcmalloc

3.1.2 内存池

1. 什么是内存池

  1. 内存池(Memory Pool) 是一种 预先分配 一块大内存,然后按需分配和回收 其中小块内存的技术。它的本质是管理一块连续的大内存区域,避免频繁调用 malloc/free 或 new/delete,提高性能并减少内存碎片。

  2. 池(线程池、内存池等等)->起一个缓冲作用

  3. 运行时间长->coredump,内存碎片-> 内存管理组件(内存池)

  4. 线程池代码都差不多,内存池(指的是虚拟内存管理,就是堆管理)就不一样了

从高地址向低地址增长
从低地址向高地址增长
只读
内核代码与数据
用户空间 User Space
栈 Stack
堆 Heap
数据段 Data Segment
BSS 段 BSS Segment
代码段 Text Segment
内核空间 Kernel Space

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, 128
	int 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) {  //NULL
		return -2;
	}
	memset(m->mem, 0, MEM_PAGE_SIZE); // 都置0

	m->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; // 最后一块的指针指向 NULL


	return 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_memcpy

int 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);
}

  1. 释放完的块,如何被再次使用
    在定长(固定大小)内存池中,释放后的内存块通常通过**空闲链表(Free List)**管理,以便后续分配时可以快速复用
  2. 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 TD
    A[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 -->|分配大块内存| F
    F --> G[mp_free]
    G --> H[mp_destory]
    F --> I[mp_alloc_block]  
    C --> J[mp_node_t]
    
    subgraph A [创建内存池]
        A1[创建内存池]
        A2[初始化小块内存]
        A3[初始化大块内存]
    end
    
    subgraph D [分配内存块]
        D1[检查是否有足够空间]
        D2[创建新内存块并添加到链表]
    end
    
    subgraph F [分配内存]
        F1[根据大小决定分配]
        F2[调用对应的分配函数]
    end

    subgraph G [释放内存]
        G1[释放小块内存]
        G2[释放大块内存]
    end
    
    subgraph H [销毁内存池]
        H1[释放所有内存块]
    end

    A1 --> A2
    A2 --> A3
    D1 --> D2
    F1 --> F2
    G1 --> G2
    H1 --> H

3. jemalloc

  • 适用于高并发、多线程应用(如数据库、缓存系统)
  • 低碎片率,更适合长期运行的服务
  • 支持 madvise() 释放未使用内存,降低 RSS 占用

4. tcmalloc

  • 超快的小对象分配,减少 malloc/free 开销
  • 适用于 Google 生态,如 gRPC、Go 语言 runtime
  • 批量释放机制,但不会主动归还系统内存
特性jemalloctcmalloc
线程缓存✅ 线程私有 tcache,减少锁竞争✅ 线程本地缓存(Thread-local Cache)
碎片率✅ 更低,适合长期运行的系统❌ 相对较高
小对象分配✅ 使用 Bins 机制优化✅ 高效,适用于小对象高频申请
大对象管理Extents 细粒度管理mmap 直接分配,减少锁争用
释放策略madvise() 归还内存❌ 批量回收,但不主动归还系统
适用场景✅ Redis、MySQL、MongoDB✅ Google 生态、Go runtime、gRPC

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot + Vue 的餐厅点餐管理系统

SpringBootVue餐厅点餐管理系统 技术框架 后端&#xff1a;springboot mybatisPlus前端&#xff1a;Vue2 elementUI数据库&#xff1a;mysql项目构建工具&#xff1a;maven 数据库表 14张 角色及功能 管理员&#xff1a;登录、用户管理、餐桌信息管理、菜品类型管理、菜…

【博客节选】再谈Unity 的 root motion

节选自 【Unity实战笔记】第二十三 root motion变更方向攻击 &#xff08;OnStateMove rootmotion rigidbody 使用的一些问题&#xff09; 小伙伴们应该对root motion非常困惑&#xff0c;包括那个bake into pose。 当xz bake into pose后&#xff0c;角色攻击动画与父节点产…

26考研——栈、队列和数组_栈(3)

408答疑 文章目录 一、栈1、栈&#xff08;Stack&#xff09;的概念和特点定义术语操作特性示例直观理解栈的基本操作初始化栈判断栈是否为空入栈操作出栈操作读取栈顶元素销毁栈 栈的数学性质 2、栈的顺序存储结构顺序栈的定义栈顶指针初始化注意事项 共享栈共享栈的操作共享栈…

基于 mxgraph 实现流程图

mxgraph 可以实现复杂的流程图绘制。mxGraph里的Graph指的是图论(Graph Theory)里的图而不是柱状图、饼图和甘特图等图(chart)&#xff0c;因此想找这些图的读者可以结束阅读了。 作为图论的图&#xff0c;它包含点和边&#xff0c;如下图所示。 交通图 横道图 架构图 mxGrap…

动态路由机制MoE专家库架构在多医疗AI专家协同会诊中的应用探析

随着医疗人工智能技术的飞速进步,AI在医学领域的应用日益增多,尤其是在复杂疾病的诊断和治疗中,AI技术的应用带来了巨大的潜力。特别是动态路由机制混合专家(Mixture of Experts,MoE)架构,因其灵活、高效的特点,正逐渐成为实现多AI专家协同会诊的关键技术。通过将多个不…

双工通信:WebSocket服务

&#xff08;一&#xff09;WebSocket概述 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c; 并进行双向数据传输 注意;Websocket也只能由客户端先握…

洪水灌溉算法 + 总结

文章目录 floodfill算法图像渲染题解代码 岛屿数量题解代码 岛屿的最大面积题解代码 被围绕的区域题解代码 太平洋大西洋水流问题题解代码 扫雷游戏题解代码 衣橱整理题解代码 总结 floodfill算法 1. 寻找相同性质的联通块&#xff0c;可以使用dfs或者bfs解决&#xff0c;比如…

LangChain4j(1):初识LangChain4j

1 什么是LangChain和LangChain4j LangChain是一个大模型的开发框架&#xff0c;使用LangChain框架&#xff0c;程序员可以更好的利用大模型的能力&#xff0c;大大提高编程效率。如果你是一个lava程序员&#xff0c;那么对LangChain最简单直观的理解就是&#xff0c;LangChain…

Photoshop 2025安装包下载及Photoshop 2025详细图文安装教程

文章目录 前言一、Photoshop 2025安装包下载二、Photoshop 2025安装教程1.解压安装包2.运行程序3.修改安装路径4.设安装目录5.开始安装6.等安装完成7.关闭安装向导8.启动软件9.安装完成 前言 无论你是专业设计师&#xff0c;还是初涉图像处理的小白&#xff0c;Photoshop 2025…

SQL Server安装程序无法启动:系统兼容性检查失败

问题现象&#xff1a; 运行 SQL Server 2022 安装程序时&#xff0c;提示 “硬件或软件不满足最低要求”&#xff0c;安装向导直接退出或无法继续。 快速诊断 操作系统版本检查&#xff1a; # 查看 Windows 版本&#xff08;需 20H2 或更高&#xff09; winver 支持的系统&…

期权合约作废的话,权利金和保证金会退还么?

在期权交易中&#xff0c;权利金是否可以退回&#xff0c;主要取决于期权的交易情况和合约条款。 期权作废的三种情形 一般来说期权作废一共有三种情况&#xff0c;分别是到期没有行权、主动放弃或者是标的退市了。 第一种是到期未行权&#xff0c;一般来说值得都是虚值期权&…

MIPI计算ECC和CRC工具介绍

一、MIPI简介 MIPI联盟&#xff0c;即移动产业处理器接口&#xff08;Mobile Industry Processor Interface 简称MIPI&#xff09;联盟。MIPI&#xff08;移动产业处理器接口&#xff09;是MIPI联盟发起的为移动应用处理器制定的开放标准和一个规范。MIPI官网https://mipi.org/…

医院管理系统(源码)分享

「医院管理系统&#xff08;源码&#xff09; 源码&#xff1a; https://pan.quark.cn/s/b6e21488fce3 第1章 绪论 1.1 项目背景 随着计算机科学的迅猛发展和互联网技术的不断推进&#xff0c;人们的生活方式发生了巨大的变化&#xff0c;同时也推动了整个软件产业的发展。把…

使用Geotools从DEM数据中读取指定位置的高程实战

目录 前言 一、GridCoverage2D对象介绍 1、GridCoverage2D的属性 2、GridCoverage2D核心方法 3、GridCoverage2D中的高级操作 二、指定位置的高程获取 1、存储原理 2、相关属性的获取 3、获取高程的方法 三、总结 前言 在地理信息科学领域&#xff0c;高程数据是至关重…

STM32F103_LL库+寄存器学习笔记05 - GPIO输入模式,捕获上升沿进入中断回调

导言 GPIO设置输入模式后&#xff0c;一般会用轮询的方式去查看GPIO的电平状态。比如&#xff0c;最常用的案例是用于检测按钮的当前状态&#xff08;是按下还是没按下&#xff09;。中断的使用一般用于计算脉冲的频率与计算脉冲的数量。 项目地址&#xff1a;https://github.…

直播预告 | TDgpt 智能体发布 时序数据库 TDengine 3.3.6 发布会即将开启

从海量监控数据&#xff0c;到工业、能源、交通等场景中实时更新的各类传感器数据&#xff0c;时序数据正在以指数级速度增长。而面对如此庞杂的数据&#xff0c;如何快速分析、自动发现问题、精准预测未来&#xff0c;成为企业数字化转型过程中的关键挑战。 TDengine 的答案是…

vscode 通过Remote-ssh远程连接服务器报错 could not establish connection to ubuntu

vscode 通过Remote-ssh插件远程连接服务器报错 could not establish connection to ubuntu&#xff0c;并且出现下面的错误打印&#xff1a; [21:00:57.307] Log Level: 2 [21:00:57.350] SSH Resolver called for "ssh-remoteubuntu", attempt 1 [21:00:57.359] r…

【JavaScript 简明入门教程】为了Screeps服务的纯JS入门教程

0 前言 0-1 Screeps: World 众所不周知&#xff0c;​Screeps: World是一款面向编程爱好者的开源大型多人在线即时战略&#xff08;MMORTS&#xff09;沙盒游戏&#xff0c;其核心机制是通过编写JavaScript代码来控制游戏中的单位&#xff08;称为“Creep”&#xff09;&#…

Prometheus stack命令行接入springboot服务metrics

使用Prometheus Stack监控SpringBoot应用 本文将详细介绍如何使用Prometheus Stack监控SpringBoot应用的metrics。假设你已经安装了Kubernetes集群&#xff0c;并使用Helm安装了Prometheus Stack全家桶。SpringBoot应用已经配置好&#xff0c;暴露了相应的metrics端点。 Sprin…

Git Bash 设置Notepad++作为默认编辑器

网上搜的时候发现别人搞得有点复杂 &#xff08;绝对正确的方法&#xff09;Git Bash 设置Notepad作为默认编辑器_git 通过notpad 编辑器-CSDN博客 最简单的方式就是重新安装git&#xff0c;然后在选择编辑器的时候&#xff0c;勾选notepad即可