如何在 C++ 中实现自定义内存分配器

在高性能 C++ 程序中,内存分配往往成为瓶颈。标准的 new/delete 可能会产生频繁的系统调用、碎片化或竞争。通过实现自定义内存分配器,可以针对特定需求进行优化。本文将从设计目标、实现步骤和性能评测三方面展开。

一、设计目标

  1. 降低分配/释放成本:使用一次性内存池或按页分配,减少系统调用次数。
  2. 内存对齐:保证所有分配对象满足所需对齐要求,避免未对齐访问导致的性能下降或异常。
  3. 可追踪泄漏:在调试模式下能够记录分配信息,帮助定位泄漏。
  4. 线程安全:在多线程环境下安全使用,或通过线程局部分配器实现无锁分配。

二、实现步骤

下面给出一个简易的固定大小块内存池(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)

四、进阶方向

  1. 可变块分配:结合 std::allocator 接口,实现可变大小内存池。
  2. 内存对齐:使用 std::align 或平台特定 API(_aligned_malloc)满足对齐需求。
  3. 内存回收:在池不足时可实现分块扩容或与系统堆交互。
  4. 泄漏检测:在 debug 模式下记录分配/释放调用栈。

通过上述步骤,你可以为自己的 C++ 项目编写一个高效、可定制的内存分配器,显著提升程序的性能与可维护性。

发表评论