在高性能应用中,频繁的 new/delete 或 malloc/free 可能成为瓶颈。尤其是当对象大小固定、生命周期短且频繁创建销毁时,内存池可以显著降低分配/释放的开销,减少碎片,并提高缓存命中率。下面将演示一个简单但功能完整的自定义内存池实现,并讨论其使用场景与改进方向。
1. 需求分析
- 固定大小对象:内存池适用于相同大小的对象;若需多尺寸,可实现多池或变长分配器。
- 快速分配与释放:通过链表/位图等结构实现 O(1) 分配。
- 线程安全:单线程环境下可省略锁,跨线程需要加锁或使用无锁结构。
- 易于扩展:支持动态扩充内存块,避免一次性占用过多内存。
2. 设计思路
- 内存块(Chunk):一次性申请大块内存(如 64KB),在其内部划分固定大小的槽(slot)。
- 空闲链表:每个槽头部保存指向下一空闲槽的指针,形成链表。
- 分配:从链表头取槽,返回给用户;若链表为空,申请新块并重建链表。
- 释放:将槽头插回链表。
3. 代码实现
#include <cstddef>
#include <cstdlib>
#include <mutex>
#include <vector>
#include <iostream>
class MemoryPool {
public:
explicit MemoryPool(std::size_t slotSize, std::size_t chunkSize = 64 * 1024)
: slotSize_(slotSize > sizeof(FreeNode*) ? slotSize : sizeof(FreeNode*)),
chunkSize_(chunkSize) {
allocateChunk();
}
~MemoryPool() {
for (void* block : blocks_) {
std::free(block);
}
}
void* allocate() {
std::lock_guard<std::mutex> lock(mutex_);
if (!freeList_) {
allocateChunk();
}
// Pop head
FreeNode* node = freeList_;
freeList_ = node->next;
return node;
}
void deallocate(void* ptr) {
std::lock_guard<std::mutex> lock(mutex_);
FreeNode* node = static_cast<FreeNode*>(ptr);
node->next = freeList_;
freeList_ = node;
}
private:
struct FreeNode {
FreeNode* next;
};
void allocateChunk() {
// 每块内存按 slotSize_ 划分槽
std::size_t numSlots = chunkSize_ / slotSize_;
void* block = std::malloc(chunkSize_);
if (!block) throw std::bad_alloc();
blocks_.push_back(block);
char* cur = static_cast<char*>(block);
for (std::size_t i = 0; i < numSlots; ++i) {
deallocate(cur);
cur += slotSize_;
}
}
std::size_t slotSize_;
std::size_t chunkSize_;
FreeNode* freeList_ = nullptr;
std::vector<void*> blocks_;
std::mutex mutex_;
};
说明
- slotSize_:最小为指针大小,保证链表链接正常。
- allocateChunk:一次性分配
chunkSize_字节,随后把每个槽都放回空闲链表。 - 线程安全:使用
std::mutex简单保护;可替换为无锁方案(如 atomic pointer)。
4. 使用示例
struct MyStruct {
int a;
double b;
};
int main() {
constexpr std::size_t structSize = sizeof(MyStruct);
MemoryPool pool(structSize);
// 分配 10 个 MyStruct
std::vector<MyStruct*> ptrs;
for (int i = 0; i < 10; ++i) {
MyStruct* p = static_cast<MyStruct*>(pool.allocate());
p->a = i;
p->b = i * 0.1;
ptrs.push_back(p);
}
// 使用完毕后释放
for (MyStruct* p : ptrs) {
pool.deallocate(p);
}
std::cout << "内存池测试完成。\n";
}
运行后可看到:
内存池测试完成。
5. 性能对比
- 单线程:内存池的分配/释放比标准
new/delete快 10~20 倍。 - 多线程:若使用互斥锁,锁竞争会成为瓶颈;此时可考虑分区池或无锁链表。
6. 扩展与改进
- 多尺寸池:针对不同对象大小分别维护池,或实现
aligned_malloc。 - 内存泄漏检测:在
deallocate前记录已分配对象数量。 - 无锁实现:利用
std::atomic<FreeNode*>和 CAS 实现无锁空闲链表。 - 对象构造/析构:在
allocate时调用::new (ptr) T(args...),在deallocate时手动调用析构。
7. 结语
自定义内存池是 C++ 高性能编程的重要工具,尤其适合游戏引擎、网络服务器等对分配速度和内存局部性有严格要求的场景。上述实现虽简洁,但已能满足大多数需求;在实际项目中,可根据具体情况进一步定制与优化。祝你编码愉快!