A07 —— 内存池:预先分配,按需分配,避免碎片化
金句:在需要频繁分配和释放的场景下,预分配一个池比每次向系统要快 100 倍。
什么是内存池
内存池(Memory Pool)是一种预先分配一大块内存,然后按需分配给应用程序的技术。相比每次调用 malloc,内存池减少了系统调用和碎片化。
传统 malloc 的问题:
高频分配/释放场景(如游戏服务器):
- 每帧可能分配/释放几百到几千个小对象
- 网络包、对象、事件等
- malloc/free 有锁竞争 + 系统调用开销
内存池的解决方案:
1. 启动时分配一大块内存(内存池)
2. 维护一个空闲链表
3. 分配:从空闲链表取(O(1),无锁,无系统调用)
4. 释放:放回空闲链表(O(1),无锁)
性能对比(分配释放 100 万次 32-byte 对象):
- malloc/free:约 2~5 秒
- 内存池:约 0.01~0.02 秒
→ 100~500x 性能提升固定大小内存池
最简单的内存池:固定大小对象池
template<typename T>
class MemoryPool {
struct Block {
Block *next;
char data[sizeof(T)];
};
Block *free_list = nullptr;
public:
T* allocate() {
if (!free_list) {
// 扩展池(批量分配)
for (int i = 0; i < 1024; i++) {
Block *new_block = (Block*)malloc(sizeof(Block));
new_block->next = free_list;
free_list = new_block;
}
}
Block *block = free_list;
free_list = block->next;
return reinterpret_cast<T*>(block);
}
void deallocate(T *p) {
Block *block = reinterpret_cast<Block*>(p);
block->next = free_list;
free_list = block;
}
};
特点:
- 所有对象大小相同
- 空闲链表管理
- O(1) 分配/释放
- 无碎片(固定大小)内存池的实际应用
游戏服务器的内存池:
服务器每帧处理:
- 1000 个网络包(每个 256 bytes)
- 500 个实体对象(每个 64 bytes)
- 200 个事件(每个 32 bytes)
没有内存池:
- 每帧 malloc 1700 次,free 1700 次
- 锁竞争 + 系统调用
- 碎片化严重
有内存池:
- 启动时分配 3 个池(256/64/32 bytes)
- 每帧从池分配,帧末归还池
- 无锁竞争(per-thread 池)
- 无碎片
实现示例(per-thread pool):
struct PacketPool {
static thread_local PacketPool instance;
Packet *allocate() {
if (free_list) {
Packet *p = free_list;
free_list = p->next;
return p;
}
return nullptr; // 池空,需要扩展
}
void deallocate(Packet *p) {
p->next = free_list;
free_list = p;
}
Packet *free_list = nullptr;
};使用内存池的库:
Boost.Pool:
#include <boost/pool/pool.hpp>
boost::pool<> p(sizeof(MyClass));
MyClass *obj = (MyClass*)p.malloc(); // 分配
p.free(obj); // 释放
tcmalloc 的对象缓存:
- tcmalloc 内部有 per-thread 对象池
- 不同 size 的对象有不同的池
- 分配/释放都是 O(1)
jemalloc 的 bin:
- 每个 size class 一个 bin
- 类似内存池的思想
- 比 ptmalloc 更高效Slab Allocator(与内核 SLAB 相同思想)
Slab Allocator = 内存池 + 对象缓存
游戏服务器中的 Slab:
class SlabAllocator {
struct Slab {
void *mem; // 分配的内存块
int object_size; // 对象大小
int count; // 对象数量
int free_count; // 空闲对象数
void *free_list; // 空闲链表
};
std::vector<Slab> slabs;
public:
void create_slab(int object_size, int count) {
Slab s;
s.mem = malloc(object_size * count);
s.object_size = object_size;
s.count = count;
s.free_count = count;
// 初始化 free_list(所有对象串起来)
slabs.push_back(s);
}
void* allocate(int object_size) {
for (auto& slab : slabs) {
if (slab.object_size == object_size && slab.free_count > 0) {
void *obj = slab.free_list;
slab.free_list = *(void**)obj;
slab.free_count--;
return obj;
}
}
return nullptr; // 没有可用的 slab
}
};
特点:
- 每个 slab 管理固定大小的对象
- 批量分配(创建 slab 时)
- 分配/释放 O(1)
- 适合已知对象大小的场景总结
- 内存池:预先分配大块,按需分配,无系统调用,无锁竞争
- 固定大小池:所有对象相同大小,空闲链表管理,O(1) 分配/释放
- 性能:100~500x 提升(vs malloc/free),适合高频分配场景
- 应用:游戏服务器、网络包处理、实时系统
- Slab:每个 size class 一个 slab,批量分配,O(1) 操作
下篇预告(A08):什么是野指针和悬空指针?它们和 UAF 有什么区别,以及怎么避免。
关注公众号「AI不着急」,回复”资料”获取内存学习路线图。
评论