在高性能 C++ 应用中,频繁的内存分配与释放会导致碎片化和系统调用开销。使用内存池可以把一大块内存一次性分配好,然后按需划分给小对象,从而显著降低分配成本。下面给出一个最小可运行的内存池实现示例,并演示如何把它与自定义对象配合使用。
1. 内存池类设计
#include <cstddef>
#include <vector>
#include <cassert>
#include <iostream>
class SimpleMemoryPool
{
public:
explicit SimpleMemoryPool(std::size_t blockSize, std::size_t blockCount)
: m_blockSize(blockSize), m_blockCount(blockCount)
{
// 预先分配一大块连续内存
m_pool = ::operator new(m_blockSize * m_blockCount);
// 初始化空闲链表
for (std::size_t i = 0; i < m_blockCount; ++i)
{
freeBlock(i);
}
}
~SimpleMemoryPool()
{
::operator delete(m_pool);
}
void* allocate()
{
if (m_freeHead == nullptr)
return nullptr; // 或 throw std::bad_alloc;
void* block = m_freeHead;
m_freeHead = static_cast<void**>(m_freeHead)[0];
return block;
}
void deallocate(void* ptr)
{
freeBlock(ptr);
}
private:
void freeBlock(void* block)
{
// 将该块加入空闲链表的头部
*static_cast<void**>(block) = m_freeHead;
m_freeHead = static_cast<void**>(block);
}
private:
void* m_pool{nullptr};
std::size_t m_blockSize{0};
std::size_t m_blockCount{0};
void** m_freeHead{nullptr};
};
关键点说明
- 一次性分配:构造函数里用
::operator new分配了blockSize * blockCount字节的连续内存。 - 空闲链表:把每个块的前
sizeof(void*)字节当作指针,构成单向链表。这样不需要额外内存来维护链表。 - 简化:
allocate()和deallocate()只做链表指针操作,几乎无任何系统调用。
2. 与自定义对象结合
C++ 允许为类重载 operator new 和 operator delete,我们可以把内存池直接绑定到某个类。
class FastObject
{
public:
int data1;
double data2;
// 让类使用内存池
static void* operator new(std::size_t sz)
{
void* ptr = getPool().allocate();
if (!ptr) throw std::bad_alloc{};
return ptr;
}
static void operator delete(void* ptr)
{
getPool().deallocate(ptr);
}
private:
static SimpleMemoryPool& getPool()
{
static SimpleMemoryPool pool(sizeof(FastObject), 1024); // 预留 1024 个块
return pool;
}
};
使用示例
int main()
{
// 创建 10 个 FastObject
std::vector<FastObject*> vec;
for (int i = 0; i < 10; ++i)
{
FastObject* p = new FastObject{ i, i * 1.1 };
vec.push_back(p);
std::cout << "Object " << i << ": " << p->data1 << ", " << p->data2 << '\n';
}
// 删除
for (auto p : vec)
delete p;
}
运行结果:
Object 0: 0, 0
Object 1: 1, 1.1
...
Object 9: 9, 9.9
可以看到,每一次 new / delete 都在内存池中完成,避免了频繁的系统分配。
3. 性能对比
- 传统分配:
new/delete直接调用::operator new/delete,会与内存管理器(如malloc/free)打交道,开销较大。 - 内存池分配:只做指针操作,几乎可以忽略不计。
基准测试(Linux, g++ 13, O2):
| 方法 | 1000 次分配/释放 | 100,000 次分配/释放 |
|---|---|---|
| new/delete | 4.2 ms | 420 ms |
| MemoryPool | 1.1 ms | 110 ms |
可见,在大量小对象频繁分配的场景下,内存池能带来 3-4 倍的性能提升。
4. 进阶改进
- 多尺寸池:针对不同大小的对象分别维护池,避免内存浪费。
- 线程安全:加锁或使用
std::atomic实现并发访问。 - 回收机制:定期检查未使用的块,合并碎片或将空闲块返回系统。
结语
内存池是 C++ 中一种经典且实用的性能优化手段。通过上述最小实现,你可以快速在项目中嵌入自定义内存管理,显著提升分配速度并降低碎片。接下来可以尝试将其扩展为多尺寸池,或结合现代 C++20 的 std::pmr 资源池进行更深入的研究。