内存池
内存池
内存池(Memory Pool)是一种提前分配一大块内存、并在之后反复复用这块内存来分配/释放对象的技术,目的是为了提高性能、降低内存碎片、避免频繁调用 malloc/new 等昂贵操作。
为什么要用内存池
标准的 new / malloc 调用:
- 都涉及系统调用(如
sbrk,mmap),慢; - 易产生碎片;
- 无法保证内存地址的连续性;
- 每次创建/销毁对象都有构造/析构与堆管理开销。
内存池的优势:
| 优点 | 描述 |
|---|---|
| 快速分配 | 内部通过指针或索引跳转,分配速度远快于 malloc/new |
| 控制碎片 | 一次性分配大内存块,减少碎片 |
| 可重用 | 对象销毁后可复用内存,无需反复系统分配 |
| 可预测性高 | 特别适合实时系统、游戏、数据库、嵌入式等对性能要求极高的场景 |
工作原理概览
-
预分配一块大内存(如
char pool[1024 * 1024]) -
把这块内存分成若干个固定大小的块(或管理可变大小)
-
维护一个空闲链表/栈来跟踪哪些块可用
-
alloc():从空闲列表中拿出一块,构造对象(用placement new) -
free():调用析构函数,把内存块归还空闲列表
简单示例:固定大小内存池
// 内存池模板类:管理固定数量(N)对象(类型为 T)的内存复用
template <typename T, size_t N>
class MemoryPool {
private:
// 原始内存块,用于存储 N 个对象的原始字节
// alignas(T) 确保 buffer 对齐到类型 T 的对齐要求,避免未定义行为
alignas(T) char buffer[N * sizeof(T)];
// 空闲列表,保存当前可用的对象地址
std::vector<T*> free_list;
public:
// 构造函数:初始化空闲列表,将 buffer 切分成 N 个对象大小的块
MemoryPool() {
for (size_t i = 0; i < N; ++i) {
// 将 buffer 中每个对象位置转换为 T* 并加入空闲列表
free_list.push_back(reinterpret_cast<T*>(&buffer[i * sizeof(T)]));
}
}
// 分配一个对象
T* allocate() {
// 如果没有可用内存,抛出异常
if (free_list.empty()) throw std::bad_alloc();
// 从空闲列表中取出一个空闲块
T* ptr = free_list.back();
free_list.pop_back();
// 在该内存位置上使用 placement new 构造对象
return new (ptr) T();
}
// 回收一个对象
void deallocate(T* ptr) {
// 显式调用析构函数,释放对象资源
ptr->~T();
// 将内存块重新加入空闲列表,以便后续复用
free_list.push_back(ptr);
}
};
使用示例:
struct MyObject {
int x;
MyObject() { std::cout << "Constructor\n"; }
~MyObject() { std::cout << "Destructor\n"; }
};
int main() {
MemoryPool<MyObject, 10> pool;
MyObject* obj1 = pool.allocate(); // 从池中分配一个对象
pool.deallocate(obj1); // 回收对象
return 0;
}
STL 容器也支持内存池
STL 的 allocator 机制允许我们自定义内存分配器:
std::vector<int, MyMemoryPoolAllocator<int>> vec;
这让 vector/map/set 等容器内部元素也能通过内存池分配,提高性能。
注意事项
| 项目 | 说明 |
|---|---|
| 析构函数需手动调用 | 内存池只分配空间,不会自动析构对象 |
| 不支持动态大小对象 | 固定块大小的内存池不适合 std::string、vector 等变长对象 |
| 线程安全性 | 多线程环境需加锁或使用线程局部内存池 |
| 内存泄漏风险 | 使用错误容易忘记回收或析构对象 |

浙公网安备 33010602011771号