最近开始学习内存池技术,《高质量c++/c编程指南》在内存管理的第一句话就是:欢迎进入内存这片雷区,由此可见掌握内存管理对于c++/c程序员的重要性。使用内存池的优点有:降低动态申请内存的次数,提升系统性能,减少内存碎片,增加内存空间使用率。
内存池的分类:
一、不定长内存池:优点:不需要为不同的数据创建不同的内存池,缺点是分配出去的内存池不能回收到池中(?)。代表有apr_pool,obstack。
二、定长内存池:优点:使用完立即把内存归还池中。代表有Loki, Boost。
本次以sgi stl中实现的内存池作为学习对象。由于要实现的是一个C语言的内存池,所以这里用C的描述方式。喜欢C++的朋友可以直接看源文件或者《STL源码剖析》的讲解。sgi设计了二级配置机制,第一级配置器直接使用malloc()和free()。当配置区块超过128 bytes时,则采用第一级配置器;否则采用memory pool方式。
memory pool的整体思想是维护128/8 = 16个自由链表,这里的8是小型区块的上调边界。每个自由链表串接的区块大小如下:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
串接区块 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 |
范围 | 1-8 | 9-16 | 17-24 | 25-32 | 33-40 | 41-48 | 49-56 | 57-64 | 65-72 | 73-80 | 81-88 | 89-96 | 97-104 | 105-112 | 113-120 | 121-128 |
几个过程的思路:
一、申请过程:
- if (用户申请的内存不大于128 bytes)
- 查找对应的链表
- if (对应链表拥有一块以上的区块)
- 调整链表
- 返回第一个区块地址
- else
- 准备重新填充链表
- 向内存池申请内存(指定数量的区块)
- if (内存池申请到一个区块)
- 返回第一个区块地址
- else
- 调整链表,将区块串接起来
- 返回第一个区块地址
- else
- 直接用malloc()申请内存
二、释放过程:
- if (用户释放的内存块大于128 bytes)
- 直接用free()释放
- else
- 查找对应的链表
- 回收内存
三、向内存池申请内存过程:
- if (内存池空间完全满足需求量)
- 调整内存池起始位置
- 返回空间地址
- else if (内存池空间不能完全满足需求量,但能提供一个以上的区块)
- 计算能够提供的最大内存
- 调整内存池起始位置
- 返回空间地址
- else
- 从内存池中压缩内存
- 收集比size大的空间
- 递归调用,修正nobjs
- 再次申请内存,可能抛出异常