内存池

网盘课
malloc和free对小块内存频繁管理效率低

对比传统 malloc/free

传统的 malloc/free 会导致严重的碎片问题:

  • 外部碎片:频繁分配 / 释放不同大小的内存会在堆中留下许多无法利用的小空闲块
  • 内部碎片:每次分配都会有元数据开销(如块大小、标志位等),且可能因对齐产生额外空间浪费

而自由链表通过分类管理、复用机制和批量操作,显著减少了这两种碎片。

总结

自由链表减少内存碎片的核心在于:

  1. 固定大小块:避免随机大小分配导致的间隙
  2. 即时复用:释放的内存立即被回收再利用
  3. 批量分配:减少与操作系统的交互,保持内存连续性
  4. 对齐优化:消除因对齐需求产生的额外开销

设计思路

1. 减少系统调用开销

  • 传统的 malloc/free 或 new/delete 涉及系统调用,需要从用户态切换到内核态,成本较高。
  • 内存池通过一次性分配大块内存(如几 KB 或几 MB),将多次小内存分配转化为一次大内存分配,减少系统调用次数。

2. 降低内存碎片

  • 频繁分配和释放不同大小的内存会导致内存碎片化(外部碎片和内部碎片)。
  • 内存池通过固定大小块分配对象复用机制,避免产生无法利用的小空闲块。

3. 提高分配效率

  • 系统级分配器需要维护复杂的内存状态信息(如空闲链表、位图等),查找合适的内存块耗时较长。
  • 内存池采用简单的数据结构(如链表、数组)管理内存,分配和释放操作通常是 O (1) 时间复杂度。

4. 自定义内存策略

  • 针对特定场景(如频繁创建销毁小对象)优化分配策略。
  • 支持内存预分配、对象池化、批量释放等特性。

基本原理

1. 预分配内存

  • 内存池初始化时,向操作系统申请一块连续的内存区域(称为内存池块内存池 Chunk)。

  • 这块内存通常比单次请求的内存大得多,例如:

    cpp

    char* memory_pool = new char[1024 * 1024]; // 预分配1MB内存
    

2. 内存块管理

  • 将预分配的内存划分为多个内存块(Memory Block),根据分配策略不同,块大小可以是:
    • 固定大小(如 8B、16B、32B 等):适合管理相同类型的小对象。
    • 可变大小:根据实际需求动态划分,但管理复杂度较高。

3. 分配器接口

  • 提供自定义的 allocate() 和 deallocate() 函数,替代 malloc 和 free

    cpp

    void* allocate(size_t size);    // 从内存池分配指定大小的内存
    void deallocate(void* ptr);     // 将内存块返回给内存池
    

4. 空闲列表(Free List)

  • 最常见的实现方式是维护一个空闲内存块链表
    • 每个空闲块的头部包含指向下一个空闲块的指针。
    • 分配时直接从链表头部取出一块,释放时将块插入链表头部。
    • 操作复杂度为 O (1)。

5. 内存池耗尽处理

  • 当预分配的内存用完时,有两种处理方式:
    • 扩展内存池:向操作系统申请新的内存块,并添加到内存池管理。
    • 回退到系统分配器:调用 malloc 分配内存,但可能破坏内存池的优化效果。

SGI STL内存池实现细节

SGI STL 的内存池采用两级分配器架构:

  1. 一级分配器:处理大块内存(>128B),直接调用 malloc 和 free
  2. 二级分配器:处理小块内存(≤128B),使用自由链表(Free List)管理。

自由链表的组织结构

  • 将内存块按 8 字节对齐分组,共 16 个链表:

    plaintext

    索引0:管理8B的内存块
    索引1:管理16B的内存块
    ...
    索引15:管理128B的内存块
    
  • 每个链表的节点结构为:

    cpp

    union _Obj {
        union _Obj* _M_free_list_link;  // 空闲时作为链表指针
        char _M_client_data[1];         // 分配后作为用户数据
    };
    

分配流程

  1. 用户请求 n 字节内存。
  2. 计算 n 对应的链表索引 i(通过 _S_freelist_index(n))。
  3. 若链表 i 非空,取出头部节点分配;否则:
    • 调用 refill() 从内存池取出 20 个新节点,1 个返回用户,19 个放入链表。
    • 若内存池不足,调用 chunk_alloc() 分配新的内存池块(通常是 2^n 大小)。

释放流程

  1. 根据指针地址计算所属链表索引 i
  2. 将节点插入链表 i 的头部。

两个辅助函数

将_bytes上调至邻近的8的倍数

enum{_ALIGN = 8};
static size_t _S_round_up(size_t _bytes)
{
	return (((_bytes)+(size_t)_ALIGN-1)&~((size_t)_ALIGN-1));
}
//结果是1~8=>8
//9~16=>16...

//ALIGN-1 = 0111
//~(ALIGN-1)=11111111 11111111 11111111 11111000

//(_bytes)+(size_t)_ALIGN-1)当_bytes>0时会产生进位,&与运算后只保留进位

返回_bytes大小的chunk位于free-list中的下标

_S_freelist_index

n>max_bytes用一级空间配置器即malloc分配
n<max_bytes用内存池分配
自由链表的增删改要保证线程安全,加锁。在出代码块时锁析构

![[Pasted image 20250514003919.png]]

_S_refill()
头为空

关键概念

1. 基本定义

(1) Chunk(大块内存)
  • 定义:从操作系统一次性分配的连续大块内存,通常是几 KB 或更大。
  • 用途:作为内存池的 “原材料”,被分割为多个小的 Block
  • 管理:由内存池直接管理,多个 Chunk 可通过链表连接。
(2) Block(小块内存)
  • 定义Chunk 被分割后的固定大小的内存单元,大小通常是 8 的倍数(如 8B、16B、...、128B)。
  • 用途:直接分配给用户或由自由链表管理。
  • 管理:通过自由链表组织,每个链表管理一种固定大小的 Block

2. 关键区别

对比项 Chunk Block
大小 通常几 KB(如 8KB、16KB) 固定大小(8B、16B、...、128B)
分配对象 操作系统(通过 malloc 用户(通过内存池的 allocate
分配频率 低频(链表耗尽时才分配新 Chunk) 高频(用户每次请求内存)
管理方式 由内存池直接管理(Chunk 链表) 由自由链表管理(按大小分类)
生命周期 长期存在,直到内存池销毁 短期存在,频繁分配 / 释放

3. 自由链表存储的内容

自由链表存储的是 Block 的地址,而非 Chunk 的地址。具体来说:

  1. 每个自由链表管理同一大小的多个 Block
  2. 链表中的每个节点是一个 Block,其前 4/8 字节(取决于指针大小)存储指向下一个 Block 的指针。
  3. 当链表为空时,内存池从 Chunk 中切分新的 Block 并加入链表。

_S_refill() 的作用

  1. 补充内存池的空闲块
    当内存池中某个特定大小(size)的内存块被耗尽时,_S_refill() 会从系统中申请一大块内存,将其分割成多个 size 大小的块,并将这些块加入到内存池的空闲链表中,供后续分配请求使用。

  2. 减少系统调用的频率
    通过一次性申请多个内存块(如 n 个),避免频繁调用 malloc 或 mmap,提升内存分配效率。

  3. 支持多层级内存池管理
    SGI STL 的内存池针对不同大小的内存块(如 8B、16B、32B 等)维护独立的空闲链表。_S_refill() 会根据请求的 size 选择对应的链表进行补充。

与 _S_chunk_alloc() 的关系

_S_refill() 通常依赖于 _S_chunk_alloc() 函数来完成实际的内存分配:

  • _S_chunk_alloc(size_t bytes):从系统申请 bytes 大小的内存块,并返回其地址。
  • _S_refill() 会调用 _S_chunk_alloc 获取大块内存,再进行分割。

当新配内存占了没分配完的备用内存且nobjs修改为1则不需要refill链接。否则把备用块插入对应大小的链表中
![[Pasted image 20250515200045.png]]
SGI优点
1.对于每一个字节数的chunk块分配,都是给出一部分进行使用,另一部分备用,备用块可以给当前字节数使用,也可以给其他字节数使用
2.对于备用内存池划分完后剩余的很小块内存,再次分配时会把小内存块分配出去,不浪费内存池
3.当指定字节数内存分配失败以后,有一个异常处理的过程,bytes到28字节所有chunk块查看,如果某个字节数有空闲chunk块,会借出使用

posted @ 2025-08-05 11:25  Los1r  阅读(25)  评论(0)    收藏  举报