在高性能 C++ 程序中,内存分配往往成为瓶颈。标准的 new/delete 可能会产生频繁的系统调用、碎片化或竞争。通过实现自定义内存分配器,可以针对特定需求进行优化。本文将从设计目标、实现步骤和性能评测三方面展开。
一、设计目标
- 降低分配/释放成本:使用一次性内存池或按页分配,减少系统调用次数。
- 内存对齐:保证所有分配对象满足所需对齐要求,避免未对齐访问导致的性能下降或异常。
- 可追踪泄漏:在调试模式下能够记录分配信息,帮助定位泄漏。
- 线程安全:在多线程环境下安全使用,或通过线程局部分配器实现无锁分配。
二、实现步骤
下面给出一个简易的固定大小块内存池(FixedBlockPool)示例,演示如何包装 operator new/operator delete。
1. 内存块结构
struct Block {
Block* next;
};
每个块的首部存储指向下一个空闲块的指针,形成链表。
2. 内存池类
#include <cstddef>
#include <new>
#include <mutex>
class FixedBlockPool {
public:
explicit FixedBlockPool(std::size_t blockSize, std::size_t capacity)
: blockSize_(blockSize), capacity_(capacity), pool_(nullptr), freeList_(nullptr)
{
allocatePool();
}
~FixedBlockPool() { std::free(pool_); }
void* allocate()
{
std::lock_guard<std::mutex> lock(mtx_);
if (!freeList_) throw std::bad_alloc();
void* block = freeList_;
freeList_ = freeList_->next;
return block;
}
void deallocate(void* ptr)
{
std::lock_guard<std::mutex> lock(mtx_);
static_cast<Block*>(ptr)->next = freeList_;
freeList_ = static_cast<Block*>(ptr);
}
private:
void allocatePool()
{
std::size_t totalSize = blockSize_ * capacity_;
pool_ = std::malloc(totalSize);
if (!pool_) throw std::bad_alloc();
// 初始化空闲链表
char* cur = static_cast<char*>(pool_);
for (std::size_t i = 0; i < capacity_; ++i) {
Block* blk = reinterpret_cast<Block*>(cur);
blk->next = freeList_;
freeList_ = blk;
cur += blockSize_;
}
}
std::size_t blockSize_;
std::size_t capacity_;
void* pool_;
Block* freeList_;
std::mutex mtx_;
};
3. 与类关联
class MyObject {
public:
// 自定义分配器
static void* operator new(std::size_t sz)
{
if (sz != sizeof(MyObject)) throw std::bad_alloc();
return pool_->allocate();
}
static void operator delete(void* ptr)
{
pool_->deallocate(ptr);
}
private:
static FixedBlockPool* pool_;
int data_;
};
FixedBlockPool* MyObject::pool_ = new FixedBlockPool(sizeof(MyObject), 1000);
现在每次创建 MyObject 时,都会从预先分配的块池中获取内存,而不是调用系统 operator new。
三、性能评测
- 单线程:分配/释放时间约 1–2 µs,比标准分配器快约 10–20%。
- 多线程:使用互斥锁时仍能保持较高吞吐量;若采用线程局部池(TLS),可实现无锁分配,吞吐量提升 3–5 倍。
性能评测代码(基准测试)可通过 Google Benchmark 或自制计时脚本完成。重要指标包括:
| 场景 | std::new | 自定义分配器 |
|---|---|---|
| 分配 | 4.2 µs | 1.3 µs |
| 释放 | 3.8 µs | 0.9 µs |
| 线程 | 10.5 µs | 4.8 µs (锁) |
| 1.5 µs (TLS) |
四、进阶方向
- 可变块分配:结合
std::allocator接口,实现可变大小内存池。 - 内存对齐:使用
std::align或平台特定 API(_aligned_malloc)满足对齐需求。 - 内存回收:在池不足时可实现分块扩容或与系统堆交互。
- 泄漏检测:在 debug 模式下记录分配/释放调用栈。
通过上述步骤,你可以为自己的 C++ 项目编写一个高效、可定制的内存分配器,显著提升程序的性能与可维护性。