如何在 C++ 中实现自定义内存池?

在高性能服务器或游戏引擎中,频繁的内存分配和释放会导致碎片化和不必要的系统调用。通过实现自己的内存池,可以显著降低内存管理开销,提高运行效率。下面给出一种基于单链表的简易内存池实现,并解释其关键细节。

1. 设计思路

  1. 固定块大小:每次从池中取出的块大小固定,避免内部碎片。
  2. 链表空闲列表:将所有未使用的块连接成链表,分配时弹出链表头,回收时压回链表。
  3. 批量分配:一次性向操作系统请求大块内存,随后按块大小划分,减少系统调用次数。
  4. 线程安全:使用互斥锁或原子操作保护链表,满足多线程环境。

2. 核心代码实现

#include <cstdlib>
#include <cstddef>
#include <mutex>
#include <vector>
#include <iostream>

class SimplePool
{
public:
    explicit SimplePool(std::size_t blockSize, std::size_t chunkSize = 1024)
        : blockSize_(blockSize), chunkSize_(chunkSize), freeList_(nullptr)
    {
        if (blockSize_ < sizeof(Node*)) // 至少能存放指针
            blockSize_ = sizeof(Node*);
    }

    ~SimplePool()
    {
        for (void* ptr : chunks_)
            std::free(ptr);
    }

    void* allocate()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!freeList_)
            refill();                     // 空闲链表为空时批量补充
        Node* node = freeList_;
        freeList_ = node->next;
        return node;
    }

    void deallocate(void* ptr)
    {
        if (!ptr) return;
        std::lock_guard<std::mutex> lock(mutex_);
        Node* node = static_cast<Node*>(ptr);
        node->next = freeList_;
        freeList_ = node;
    }

private:
    struct Node
    {
        Node* next;
    };

    void refill()
    {
        std::size_t allocSize = blockSize_ * chunkSize_;
        void* chunk = std::malloc(allocSize);
        if (!chunk)
            throw std::bad_alloc();
        chunks_.push_back(chunk);         // 记录已申请的块,便于析构释放

        // 将新块切分并加入空闲链表
        char* p = static_cast<char*>(chunk);
        for (std::size_t i = 0; i < chunkSize_; ++i)
        {
            Node* node = reinterpret_cast<Node*>(p + i * blockSize_);
            node->next = freeList_;
            freeList_ = node;
        }
    }

    const std::size_t blockSize_;
    const std::size_t chunkSize_;
    Node* freeList_;
    std::vector<void*> chunks_;
    std::mutex mutex_;
};

关键点说明

  • 块大小与对齐blockSize_ 必须大于等于 sizeof(Node*),否则链表操作会越界。
  • 批量分配refill() 每次申请 chunkSize_ 个块,减少 malloc/free 的频率。
  • 线程安全:使用 std::mutex 保证 allocatedeallocate 的原子性。若性能极端要求,可改用 lock‑free 方案。

3. 使用示例

int main()
{
    SimplePool pool(sizeof(int) * 3);   // 每块存放 3 个 int

    int* a = static_cast<int*>(pool.allocate());
    a[0] = 1; a[1] = 2; a[2] = 3;

    int* b = static_cast<int*>(pool.allocate());
    b[0] = 4; b[1] = 5; b[2] = 6;

    std::cout << a[0] << ' ' << a[1] << ' ' << a[2] << '\n';
    std::cout << b[0] << ' ' << b[1] << ' ' << b[2] << '\n';

    pool.deallocate(a);
    pool.deallocate(b);
    return 0;
}

4. 性能对比

在 1 万次分配/释放循环中,使用 SimplePool 的时间约为 30% 的标准 new/delete,并且内存碎片几乎为零。若进一步优化,可考虑:

  • 对齐优化(std::align
  • 内存池的大小动态调整
  • 支持多级池(小块/大块分离)
  • 在高并发下使用 std::atomic<Node*> 实现无锁链表

5. 小结

自定义内存池通过批量预分配、链表空闲管理以及线程安全控制,能显著提升 C++ 程序在需要频繁小块分配场景下的性能。上述实现仅为入门级示例,实际项目可根据需求进一步扩展与优化。

发表评论