1. 什么是内存池?

内存池(Memory Pool / Pool Allocator) 是一种内存管理机制,提前向系统申请一大块内存,再在这块内存里切分、分配和回收。
它相当于在用户空间建立了一层 “小型堆管理器”,避免频繁调用系统的 malloc/freenew/delete


2. 为什么需要内存池?

在 C++ 中,动态内存分配一般通过 new/deletemalloc/free 完成。但它们有几个问题:

  1. 频繁分配/释放的性能开销大

    • 系统分配器要维护复杂的堆数据结构,每次分配都要搜索合适的空闲块。
    • 对于小对象(例如节点、点云点、图优化残差项),频繁 malloc/free 会严重影响性能。
  2. 内存碎片化

    • 系统堆会因为不同大小的分配和释放产生“空洞”,导致内存利用率降低。
  3. 不可控

    • 系统分配器是通用的,无法根据应用的需求(固定大小、批量分配)优化。

内存池的目标:一次性向系统申请大块内存,然后在内部管理小块分配,避免频繁 malloc/free


3. 内存池的基本设计

3.1. 数据结构

  • 大块(Chunk):一次从系统申请的大内存,比如 1MB。
  • 小块(Block):Chunk 中切分的固定大小单位,比如 64 字节。
  • 空闲链表(Free List):回收的块串成单链表,下次分配直接取链表头。
[Chunk1: 64B|64B|64B|64B|...] -> FreeList
[Chunk2: 64B|64B|64B|64B|...] -> FreeList

3.2. 分配流程

  1. 申请时,先看 FreeList 是否有空闲块。
  2. 有 → 直接返回 FreeList 头部。
  3. 没有 → 新申请一个 Chunk,把其中的块串起来,返回一个。
  4. 时间复杂度 O(1)。

3.3. 释放流程

  1. 将释放的块头插回 FreeList。
  2. 时间复杂度 O(1)。

4. 内存池的分类

(1) 固定大小内存池(Fixed-size Memory Pool)

  • 每次分配固定大小的对象,比如 链表节点、树节点、点云点(float[3])
  • 常用方法:空闲链表(Free List)
  • 优点:简单高效,零碎片。

(2) 可变大小内存池(Variable-size Memory Pool)

  • 允许不同大小的分配,但通常会限制在某些对齐粒度上(如 8 字节对齐)。

  • 常见算法:

    • Slab Allocator(Linux 内核)
    • Buddy System(伙伴系统)
    • TCMalloc / Jemalloc(现代高性能内存分配器)

5. 内存池实现关键点

实现一个高效、安全的内存池(特别是固定大小对象池),核心就在于 数据结构的选择、边界条件的处理、并发与可扩展性


1. 内存池的总体思路

  • 一次性向操作系统申请大块内存(operator new / malloc)。
  • 在这块内存中切分成多个固定大小的 块(Block)
  • 使用 空闲链表(Free List) 管理哪些块已经释放可复用。
  • 分配时从链表头取一块,释放时把块插回链表。

2. 关键点一:块大小对齐(Alignment)

  • 为什么?
    内存分配必须保证对齐(例如 8 字节、16 字节),否则可能导致 未对齐访问,在某些 CPU 上会崩溃或性能极差。
  • 实现方式
    分配时取 max(对象大小, sizeof(void*)),保证至少能存下一个指针。
m_blockSize = (blockSize < sizeof(void*)) ? sizeof(void*) : blockSize;

3. 关键点二:空闲链表(Free List)管理

  • 原理:利用块本身存储一个指向下一个空闲块的指针,不需要额外空间。
  • 分配:取链表头(m_freeList),把 m_freeList 指向下一个。
  • 释放:把当前块插入链表头部。
// 分配
void* result = m_freeList;
m_freeList = *reinterpret_cast<void**>(m_freeList);// 释放
*reinterpret_cast<void**>(p) = m_freeList;
m_freeList = p;

这样分配/释放复杂度 O(1)


4. 关键点三:内存池扩展策略

  • 如果池子用完了怎么办?

    • 固定池:直接返回 nullptr,交给调用方处理。
    • 可扩展池:再申请一个新池,把它挂到链表上。
  • 高级实现会维护多个 Slab(内存大块),不同 Slab 可以存储相同大小对象。


5. 关键点四:Clear 与析构

  • 内存池析构时要释放系统内存。
  • 注意:如果用户忘记释放对象,Clear 会一次性销毁整个池(类似于 Arena allocator)。
void Clear() override {::operator delete(m_memory);m_memory = nullptr;m_freeList = nullptr;
}

6. 关键点五:线程安全

  • 单线程:直接用 Free List 就行。

  • 多线程:

    • 需要 std::mutex 或无锁结构(如 std::atomic 指针)。
    • 更高级的做法是 Thread Local Pool:每个线程有自己的池,减少锁竞争。

7. 关键点六:调试与安全性

  • 常见问题

    1. 重复释放(double free) → 导致循环链表。
    2. 释放非法指针 → 崩溃或污染池。
  • 解决方法

    • 在调试模式下可以维护分配计数。
    • 可以在块头添加 哨兵(Magic Number) 检查合法性。

8. 关键点七:应用场景的适配

  • 如果所有对象大小一样 → 固定大小池(最佳性能)

  • 如果有多种对象大小 → 维护 多级池,类似 TCMalloc

    • [8B, 16B, 32B, 64B …] 各自一个池。
    • 申请时根据大小选择对应池。

9. 关键点八:对象构造与析构

  • 内存池只负责 分配裸内存
  • 如果要调用构造函数 / 析构函数,需要配合 placement new 与手动析构。
// 构造对象
T* obj = new (pool.Allocate(sizeof(T))) T(args...);// 析构对象
obj->~T();
pool.Deallocate(obj, sizeof(T));

10. 关键点九:与标准库的集成

  • C++ STL 容器支持 自定义 Allocator
  • 可以写一个基于内存池的 Allocator<T>,直接让 std::vectorstd::map 使用。
  • 好处:容器内部对象分配走内存池,而不是 malloc/free

实现一个内存池的核心要点可以归纳为:

  1. 对齐:保证块大小 >= 指针大小,避免未对齐。
  2. 空闲链表:利用块内部存储 next,O(1) 分配/释放。
  3. 扩展策略:固定大小 vs 可扩展。
  4. 内存管理:析构时一次性释放大块。
  5. 线程安全:多线程需要锁或 TLS。
  6. 调试:防御性检查,避免非法释放。
  7. 多级池:支持不同大小对象。
  8. 构造/析构:配合 placement new 使用。
  9. Allocator 集成:无缝替换 STL 容器分配器。

6. 固定大小内存池的实现

下面定义一个内存池基类,内容如下:

class LIMalloc {
public:using size_type = unsigned int;virtual ~LIFSMalloc() {}virtual void* Allocate(size_type length) = 0;virtual void Deallocate(void* p, size_type length) = 0;virtual void Clear() = 0;
};

下面基于接口 LIMalloc 实现一个 固定大小内存池

#include <iostream>
#include <vector>
#include <cassert>class FixedSizePool : public LIMalloc {
public:explicit FixedSizePool(size_type blockSize, size_type blockCount): m_blockSize(blockSize < sizeof(void*) ? sizeof(void*) : blockSize),m_blockCount(blockCount) {// 一次性分配大块内存m_memory = ::operator new(m_blockSize * m_blockCount);// 构建空闲链表char* p = static_cast<char*>(m_memory);for (size_type i = 0; i < m_blockCount; ++i) {void* next = (i == m_blockCount - 1) ? nullptr : (p + m_blockSize);*reinterpret_cast<void**>(p) = next;p += m_blockSize;}m_freeList = m_memory;}~FixedSizePool() override {Clear();}void* Allocate(size_type length) override {if (length > m_blockSize || length == 0 || m_freeList == nullptr) {return nullptr;}void* result = m_freeList;m_freeList = *reinterpret_cast<void**>(m_freeList); // 下一个空闲块return result;}void Deallocate(void* p, size_type length) override {if (!p) return;// 插回空闲链表头部*reinterpret_cast<void**>(p) = m_freeList;m_freeList = p;}void Clear() override {::operator delete(m_memory);m_memory = nullptr;m_freeList = nullptr;}private:size_type m_blockSize;size_type m_blockCount;void* m_memory = nullptr;void* m_freeList = nullptr;  // 空闲链表头
};

7. 使用示例

struct Node {int x, y;
};int main() {FixedSizePool pool(sizeof(Node), 1000); // 内存池:1000 个 Node// 分配Node* n1 = static_cast<Node*>(pool.Allocate(sizeof(Node)));n1->x = 1; n1->y = 2;Node* n2 = static_cast<Node*>(pool.Allocate(sizeof(Node)));n2->x = 3; n2->y = 4;std::cout << n1->x << "," << n1->y << "\n";std::cout << n2->x << "," << n2->y << "\n";// 释放pool.Deallocate(n1, sizeof(Node));pool.Deallocate(n2, sizeof(Node));// 清空pool.Clear();
}

8. 应用场景

  • SLAM / 点云处理

    • 大量的小对象(点、特征、残差项),生命周期不同,频繁分配/释放。
    • 内存池能极大减少分配开销。
  • 图优化 / 求解器 (GTSAM / Ceres)

    • 大量的 FactorResidualBlock,每次迭代都创建和销毁。
  • 网络编程

    • TCP/UDP 数据包的缓存,通常大小固定(例如 1500B MTU)。
  • 游戏开发

    • 大量小对象(粒子、子弹、单位),高频率创建销毁。

9. 内存池 vs malloc/free 对比

特性malloc/free内存池
分配速度慢(系统堆管理)快(O(1),链表操作)
碎片化低(固定大小)
可控性可精确控制大小、数量
适合场景少量大对象大量小对象、频繁分配/释放

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

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

相关文章

测试 Next.js 应用:工具与策略

1. 引言 Next.js 作为一个基于 React 的全栈框架&#xff0c;在构建复杂 Web 应用时&#xff0c;测试是确保代码质量、功能稳定性和用户体验的关键步骤。测试可以分为单元测试、集成测试和端到端测试三种类型&#xff0c;每种类型针对不同的层面&#xff1a;单元测试验证单个组…

IP 分片和组装的具体过程

IP 分片和组装的具体过程 在这里插入图片描述 • 16 位标识(id): 唯一的标识主机发送的报文. 如果 IP 报文在数据链路层被分片了, 那么每一个片里面的这个 id 都是相同的. • 3 位标志字段: 第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到). 第二位置为 1 表示…

数据仓库OLTPOLAP维度讲解

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;大数据、Java、测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/…

OpenHarmony之编译配置白名单机制深度解析:构建系统的安全防线

一、白名单机制概述 在OpenHarmony的构建系统中&#xff0c;compile_standard_whitelist.json是一个关键的安全验证机制&#xff0c;它作为编译过程中的"守门人"&#xff0c;确保只有经过验证的组件和依赖关系才能被纳入最终构建产物。这个机制是OpenHarmony构建系统…

backward怎么计算的是torch.tensor(2.0, requires_grad=True)变量的梯度

import torch import torch.nn as nn import torch.optim as optim# 一个参数 w 2 w torch.tensor(2.0, requires_gradTrue) # 预测值 y_pred w * 3 # 6 # 真实值 y_true torch.tensor(10.0) # 损失 (预测 - 真实)^2 loss (y_pred - y_true) ** 2 # (6-10)^2 16loss.b…

戴永红×数图:重构零售空间价值,让陈列创造效益!

风雨同舟&#xff0c;智赢未来。近日&#xff0c;湖南戴永红商业连锁有限公司&#xff08;以下简称“戴永红”&#xff09;正式携手数图信息科技有限公司&#xff0c;全面启动“可视化品类空间管理”项目。以数图可视化陈列系统为引擎&#xff0c;双方将共同推进企业零售管理的…

排查Redis数据倾斜引发的性能瓶颈

以下是针对 Redis 数据倾斜问题的完整排查与优化方案&#xff0c;结合实战案例说明如何提升吞吐量和响应速度&#xff1a;一、问题现象定位1. ​性能监控异常​# Redis集群节点负载差异 $ redis-cli -c cluster nodes | grep master e1d7b... 10.0.0.1:637916379 master - 0 16…

元宇宙的硬件设备:从 VR 头显到脑机接口

1 元宇宙的主流硬件设备1.1 VR 头显&#xff1a;沉浸式体验的核心入口VR 头显是当前进入元宇宙最主要的硬件设备&#xff0c;通过封闭的显示系统为用户营造沉浸式虚拟环境。主流 VR 头显采用双屏 LCD 或 OLED 显示技术&#xff0c;单眼分辨率已从早期的 1080P 提升至 4K 级别&a…

具身智能2硬件架构(人形机器人)摘自Openloong社区

青龙人形机器人: 硬件 身体全身自由度43,手部自由度6*2,电池续航3h,运动控制算法(zmp/slip/mpc/深度学习)MPC+WBC+强化学习,54Tops(FP16),具有路径建图和自主导航能力,感官系统深度视觉传感器*3全景环视*1,具备语音识别与声源定位,可扩展嗅觉传感器 OpenLoong通…

JavaScript 性能优化:new Map vs Array.find() 查找速度深度对比

前言在前端开发中&#xff0c;我们经常需要从数据集合中查找特定元素。对于小规模数据&#xff0c;使用 Array.find()方法简单直接&#xff0c;但当数据量增大时&#xff0c;性能问题就会显现。本文将深入对比 Map和 Array.find()在数据查找方面的性能差异&#xff0c;并通过实…

栈与队列leetcode题型总结

1. 常用表格总结数据结构常见应用场景时间复杂度&#xff08;入/出/查&#xff09;LeetCode 高频题栈&#xff08;Stack&#xff09;括号匹配、单调栈、DFS入栈 O(1) / 出栈 O(1) / 查顶 O(1)20 有效的括号, 155 最小栈, 739 每日温度队列&#xff08;Queue&#xff09;层序遍历…

云原生俱乐部-RH124知识点总结(3)

写到这RH124的内容已经过半了&#xff0c;虽然内容不多&#xff0c;但是还是不太好写。因为简单的命令不想写&#xff0c;至于理解上也没什么难度&#xff0c;不过还是要保证整体内容的都要讲到。这篇文章就把RH124剩下的内容都完结吧&#xff0c;主要还剩下配置和保护SSH、管理…

安装DDNS-go

wget https://github.com/jeessy2/ddns-go/releases/download/v6.12.2/ddns-go_6.12.2_linux_x86_64.tar.gz tar zxvf ddns-go_6.12.2_linux_x86_64.tar.gz sudo ./ddns-go -s install

机器学习深度学习 所需数据的清洗实战案例 (结构清晰、万字解析、完整代码)包括机器学习方法预测缺失值的实践

矿物数据.xls矿物种类&#xff1a;A&#xff0c;B&#xff0c;C&#xff0c;D&#xff0c;E&#xff08;其中E数据只有一条&#xff0c;无法用于训练&#xff0c;直接剔除&#xff09;特征&#xff1a;序号 氯 钠 镁 硫 钙 钾 碳 溴 锶 pH 硼 氟 硒 矿物类型此数据有&#xff1…

从基础到架构的六层知识体系

第1层&#xff1a;数学与逻辑基础&#xff08;The Foundation&#xff09;&#x1f4cc; 计算机技术的根源&#xff1b;为算法分析、密码学、AI等提供理论支撑离散数学&#xff1a;集合、图论、逻辑、递归线性代数&#xff1a;机器学习、图形学基础概率与统计&#xff1a;数据分…

Flask 路由与视图函数绑定机制

Flask 路由与视图函数绑定机制 核心概念 在 Flask 框架中&#xff0c;路由&#xff08;Route&#xff09; 是连接 URL 路径与 Python 函数的桥梁&#xff0c;通过 app.route() 装饰器实现这种绑定关系&#xff0c;使得当用户访问特定 URL 时&#xff0c;对应的函数会被自动调用…

Spring 的 setter 注入可以解决某些类型的循环依赖问题

参考&#xff1a;https://blog.csdn.net/weixin_50055999/article/details/147493914?utm_sourceminiapp_weixin Setter 方法注入 (Setter Injection) 在类中提供一个 setter 方法&#xff0c;并在该方法上使用 Autowired、Resource 等注解。 代码示例 import org.springfr…

数据结构代码分享-5 链式栈

linkstack.c#include<stdio.h> #include<stdlib.h> #include"linkstack.h" //1.创建一个空的栈 void CreateEpLinkStack(linkstack_t **ptop) {*ptop NULL; } //2.入栈,ptop是传入的栈针的地址&#xff0c;data是入栈的数据 int pushLinkStack(linkstac…

数学建模Topsis法笔记

评价决策类-Topsis法学习笔记 问题的提出 生活中我们常常要进行评价&#xff0c;上一篇中的层次分析法&#xff0c;通过确定各指标的权重&#xff0c;来进行打分&#xff0c;但层次分析法决策层不能太多&#xff0c;而且构造判断矩阵相对主观。那有没有别的方法呢&#xff1f…

石英加速度计为何成为行业标杆?

在石油钻井、航空航天、工业自动化等领域&#xff0c;高精度、高可靠性的加速度测量至关重要。ER-QA-03F系列石英挠性加速度计凭借其卓越的性能和稳定的表现&#xff0c;成为静态与动态测试的理想选择。自2012年推出以来&#xff0c;该产品已交付数千台&#xff0c;并在石油钻井…