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

内存池学习笔记

引言

在 C/C++ 程序中,频繁使用new/deletemalloc/free动态分配小块内存,会导致两个问题:一是调用系统调用(或运行时库)的开销大;二是产生大量内存碎片,降低性能和内存利用率。内存池(Memory Pool)预先申请一大块内存,然后由程序自己管理分配和回收,像从池子里取水一样快速获取小块内存。

如果把系统默认的分配器比作“每次打水都要去河里舀”,那么内存池就是“在家门口挖一口井,随用随取”—— 避免了远距离奔波和碎片化混乱。


前置知识

  1. 内存碎片:外部碎片(空闲块之间穿插着已分配块,导致无法合并成大块)和内部碎片(分配块比实际需求大)。

  2. 分配器(Allocator):管理内存申请与释放的系统组件。

  3. 对象池(Object Pool):一种特定内存池,专门分配固定大小的对象。

  4. 块(Block):内存池管理的基本单元。

  5. 链表:常用空闲块链表来管理未分配的内存。


核心思想

内存池的基本想法:

  • 预先从系统申请一大块连续内存(称为pool)。

  • 将这块内存按固定大小分割成多个小块(chunk),每个小块大小通常等于或稍大于应用程序请求的对象大小。

  • 使用空闲链表(free list)将所有空闲块串起来。

  • 分配时,直接从空闲链表中取出一块(O(1))。

  • 释放时,将块插回空闲链表(O(1))。

固定大小内存池只适用于分配大小相同的对象。如果需要分配不同大小的对象,可以使用分级内存池(维护多个固定大小的池,如 8, 16, 32, … 字节)。

更高级的方案:slab 分配器(Linux 内核)、tcmalloc(Google)、jemalloc(Facebook)。


实现一个简单的固定大小内存池(C++)

cpp

#include <iostream> #include <cstdlib> #include <cassert> class MemoryPool { public: MemoryPool(size_t blockSize, size_t blockCount) : blockSize_(blockSize), blockCount_(blockCount) { // 分配一整块内存 pool_ = static_cast<char*>(std::malloc(blockSize * blockCount)); if (!pool_) throw std::bad_alloc(); // 初始化空闲链表:每个块的前 sizeof(FreeNode*) 字节存储下一个空闲块的地址 freeHead_ = reinterpret_cast<FreeNode*>(pool_); FreeNode* cur = freeHead_; for (size_t i = 1; i < blockCount; ++i) { char* nextBlock = pool_ + i * blockSize; cur->next = reinterpret_cast<FreeNode*>(nextBlock); cur = cur->next; } cur->next = nullptr; } ~MemoryPool() { std::free(pool_); } void* allocate() { if (!freeHead_) return nullptr; // 池已空 void* ptr = freeHead_; freeHead_ = freeHead_->next; return ptr; } void deallocate(void* ptr) { if (!ptr) return; // 将释放的块放回空闲链表头部 FreeNode* node = static_cast<FreeNode*>(ptr); node->next = freeHead_; freeHead_ = node; } // 禁止拷贝 MemoryPool(const MemoryPool&) = delete; MemoryPool& operator=(const MemoryPool&) = delete; private: struct FreeNode { FreeNode* next; }; char* pool_; FreeNode* freeHead_; size_t blockSize_; size_t blockCount_; }; // 使用示例 int main() { MemoryPool pool(sizeof(int), 100); int* a = (int*)pool.allocate(); *a = 42; std::cout << *a << std::endl; pool.deallocate(a); return 0; }

要点

  • 每个空闲块的前几个字节存储指向下一个空闲块的指针(侵入式链表)。

  • 分配和释放都是常数时间。

  • 只适用于固定大小,且不保证线程安全。


性能与性质

特性系统默认分配器内存池
分配速度较慢(涉及系统调用或复杂算法)极快(单链表操作)
释放速度较慢极快
内存碎片容易产生外部碎片无外部碎片(但可能有内部碎片)
适用场景通用,大小不一大量小块、大小固定或几种固定大小
线程安全通常全局锁需要额外实现

性质

  • 内存池不会将内存归还给操作系统(除非整个池销毁),适合长期运行且内存使用稳定的场景。

  • 可以重载new/delete操作符,使类对象自动使用内存池。

  • 对于多线程环境,可以给每个线程独立的本地池(thread-local pool)或使用原子操作保护空闲链表。

拓展

  • 对象池(Object Pool):与内存池类似,但管理的是已经构造好的对象,分配时返回已存在的对象实例(如数据库连接池、线程池)。

  • 池化技术:连接池、线程池、内存池都是“资源复用”思想的体现。

  • slab 分配器:Linux 内核的 slab 分配器将内存划分为不同大小的 cache,并维护每个对象的构造函数/析构函数。

  • 伙伴系统(Buddy System):另一种内存管理算法,平衡碎片和分配速度。


总结

内存池通过预先申请大块内存并自行管理分配,避免了频繁的系统调用和内存碎片,极大提升了频繁分配/释放小块内存的性能。它是高性能服务器、游戏引擎、数据库等系统中的基本组件。掌握内存池的实现原理,不仅能写出更高效的代码,还能深入理解计算机内存管理的底层机制。

http://www.zskr.cn/news/1521540.html

相关文章:

  • 2026年北京及北方市场正规铁艺制品选购全解析:从工艺参数到工程案例的行业观察 - 优质品牌商家
  • 缺失值不是数据缺陷,而是业务逻辑的信标
  • 从BERT到GPT:给NLP新手的预训练模型选型指南(附场景对比与代码示例)
  • 多维聚合实战:从GROUP BY到OLAP立方体的工程化跃迁
  • Fabric工程师必懂的五大核心决策框架
  • 电商搜索中的嵌入检索技术与对比学习应用
  • 2026年国内齿轮减速机生产厂家深度测评:技术、案例与选购指南 - 优质品牌商家
  • 汽车MCU里的‘内存保镖’:手把手配置瑞萨芯片的ECC纠错功能(附寄存器详解)
  • AI代理Runtime层的范式革命:事件日志驱动的状态管理
  • 实时数据流如何重塑AI决策能力
  • 告别命令行!用VSCode的Dev Containers在M1 Mac上秒配Java开发环境
  • 多旋翼控制分配(Control Allocation)原理与实战指南
  • 三步搞定显卡噪音:FanControl零基础调校指南
  • GPT-4参数规模与MoE稀疏激活的工程真相
  • 想发SCI四区交通类论文?聊聊这本开源期刊JAT的投稿避坑指南与APC费用详解
  • 给你的STM32项目加个“眼睛”:1.8寸ST7735屏的GUI界面快速上手教程
  • 2026长沙二手房整体翻新技术测评:长沙旧房改造价格/长沙旧房改造公司/长沙旧房改造工期/三家实力厂商工艺拆解 - 优质品牌商家
  • 物理AI落地实战:VLA模型的Agentic Skills增强方案
  • K210的KPU到底有多强?实测YOLO v2物体检测的帧率与功耗,对比树莓派Zero 2 W
  • CANN图引擎ge核心技术深度解析:从图编译优化到算子融合的昇腾NPU推理性能全链路提升实战
  • GPT-4的2%参数真相:MoE稀疏激活原理与工程实践
  • Vue3 Marquee 4.2.2:零依赖动画组件的架构解析与性能优化
  • 2026成都工商代办注册公司机构深度盘点:哪家更懂本地中小企业的真实需求? - 优质品牌商家
  • MAA明日方舟助手:高效智能的全日常自动化解决方案
  • 别再用DQN了!试试SAC:在贪吃蛇游戏中对比主流RL算法的实战效果
  • 从Uber到LinkedIn:OpenMetadata与DataHub背后的架构哲学与选型启示
  • 别再乱买了!手把手教你读懂SD卡/TF卡上的神秘标识(V30、A2、UHS-I都是啥?)
  • 别再浪费STM32的引脚了!手把手教你释放PB3/PB4/PA15这三个“特殊”IO口(基于STM32F103C8T6)
  • 企业级AI编码引擎选型:长上下文、安全治理与SDLC协同能力
  • 从51到STM32:我踩过的那些坑和高效迁移指南(Keil C51到MDK)