当前位置: 首页 > 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.jsqmd.com/news/1009734/

相关文章:

  • 别再到处找freeglut了!Windows下用Visual Studio 2022配置OpenGL ES开发环境(附3.0稳定版下载)
  • 2026年靠谱的浙江混凝土/泡沫混凝土厂家精选合集 - 品牌宣传支持者
  • LabelImg汉化包替换后总报错?可能是你的PyQt5资源编译姿势不对(附完整排错流程)
  • 解锁创维盒子E900V22C的完全体:开启adb root权限后,这5个玩法让旧盒子焕发新生
  • 机器学习落地前的四道业务安检门
  • 从Docker镜像到生产环境:kkfileview与Nginx反向代理配置的细节全解析
  • 大模型MoE架构中2%参数如何实现高效调度
  • 别再用L298N了?ESP32驱动电机方案对比:DRV8833、TB6612、L298N谁更香
  • 2026年北京及北方市场正规铁艺制品选购全解析:从工艺参数到工程案例的行业观察 - 优质品牌商家
  • DeepSeek OCR本地部署:文档识别成本降低96%的工程实践
  • 2026上海会展保洁公司怎么选?标杆推荐与实操推荐 - 优质品牌商家
  • AI模型选型的真成本:Fine-tuning、蒸馏与迁移学习的产线级ROI对比
  • 作业帮学习机2026全方位深度测评:AI辅导、护眼配置与真实口碑解析
  • 缺失值不是数据缺陷,而是业务逻辑的信标
  • 从BERT到GPT:给NLP新手的预训练模型选型指南(附场景对比与代码示例)
  • 2026年贵州中职教育口碑深度分析:哪些学校值得关注? - 优质品牌商家
  • AI资讯简报如何做到真正实用?从信息过载到可执行工作流
  • 算法不是AI:普通人可理解的决策流水线
  • 多维聚合实战:从GROUP BY到OLAP立方体的工程化跃迁
  • 2026双金属耐磨管行业深度分析:电厂、矿山场景下耐用型管材厂商对比与案例解析 - 优质品牌商家
  • 电商搜索中的嵌入检索技术与对比学习应用
  • 2026年国内齿轮减速机生产厂家深度测评:技术、案例与选购指南 - 优质品牌商家
  • Fabric工程师必懂的五大核心决策框架
  • 别再被Kafka Kerberos认证的`sasl.kerberos.service.name`搞晕了!一个配置项引发的‘血案’与避坑指南
  • 汇编调试不求人:DOSBox搭配Debug命令实战指南(从Hello World到单步追踪)
  • 终极GitHub加速指南:5分钟让你的下载速度飙升10倍
  • 2026亚洲弹性学制EMBA客观测评与理性选型指南
  • AI代理Runtime层的范式革命:事件日志驱动的状态管理
  • 实时数据流如何重塑AI决策能力
  • 2026年知名的浙江泡沫混凝土/流态固化混凝土/宁波泡沫混凝土/宁波混凝土厂家对比推荐 - 行业平台推荐