A07 — Memory Pool: Pre-allocate, On-demand Allocation, Avoiding Fragmentation

Key Insight: In scenarios with frequent allocation/deallocation, pre-allocating a pool is 100x faster than asking the system each time.


What is a Memory Pool

A Memory Pool pre-allocates a large block of memory and allocates from it on demand. Compared to calling malloc each time, memory pools reduce system calls and fragmentation.

Bash
传统 malloc 的问题:

高频分配/释放场景(如游戏服务器):
  - 每帧可能分配/释放几百到几千个小对象
  - 网络包、对象、事件等
  - malloc/free 有锁竞争 + 系统调用开销

内存池的解决方案:

1. 启动时分配一大块内存(内存池)
2. 维护一个空闲链表
3. 分配:从空闲链表取(O(1),无锁,无系统调用)
4. 释放:放回空闲链表(O(1),无锁)

性能对比(分配释放 100 万次 32-byte 对象):
  - malloc/free:约 2~5 秒
  - 内存池:约 0.01~0.02 秒
  → 100~500x 性能提升

Fixed-Size Memory Pool

Bash
最简单的内存池:固定大小对象池

template<typename T>
class MemoryPool {
    struct Block {
        Block *next;
        char data[sizeof(T)];
    };

    Block *free_list = nullptr;

public:
    T* allocate() {
        if (!free_list) {
            // 扩展池(批量分配)
            for (int i = 0; i < 1024; i++) {
                Block *new_block = (Block*)malloc(sizeof(Block));
                new_block->next = free_list;
                free_list = new_block;
            }
        }

        Block *block = free_list;
        free_list = block->next;
        return reinterpret_cast<T*>(block);
    }

    void deallocate(T *p) {
        Block *block = reinterpret_cast<Block*>(p);
        block->next = free_list;
        free_list = block;
    }
};

特点:
  - 所有对象大小相同
  - 空闲链表管理
  - O(1) 分配/释放
  - 无碎片(固定大小)

Practical Applications of Memory Pools

Bash
游戏服务器的内存池:

服务器每帧处理:
  - 1000 个网络包(每个 256 bytes)
  - 500 个实体对象(每个 64 bytes)
  - 200 个事件(每个 32 bytes)

没有内存池:
  - 每帧 malloc 1700 次,free 1700 次
  - 锁竞争 + 系统调用
  - 碎片化严重

有内存池:
  - 启动时分配 3 个池(256/64/32 bytes)
  - 每帧从池分配,帧末归还池
  - 无锁竞争(per-thread 池)
  - 无碎片

实现示例(per-thread pool):

struct PacketPool {
    static thread_local PacketPool instance;

    Packet *allocate() {
        if (free_list) {
            Packet *p = free_list;
            free_list = p->next;
            return p;
        }
        return nullptr;  // 池空,需要扩展
    }

    void deallocate(Packet *p) {
        p->next = free_list;
        free_list = p;
    }

    Packet *free_list = nullptr;
};

Bash
使用内存池的库:

Boost.Pool:
  #include <boost/pool/pool.hpp>
  boost::pool<> p(sizeof(MyClass));
  MyClass *obj = (MyClass*)p.malloc();  // 分配
  p.free(obj);                            // 释放

tcmalloc 的对象缓存:
  - tcmalloc 内部有 per-thread 对象池
  - 不同 size 的对象有不同的池
  - 分配/释放都是 O(1)

jemalloc 的 bin:
  - 每个 size class 一个 bin
  - 类似内存池的思想
  - 比 ptmalloc 更高效

Slab Allocator (Same Concept as Kernel SLAB)

Bash
Slab Allocator = 内存池 + 对象缓存

游戏服务器中的 Slab:

class SlabAllocator {
    struct Slab {
        void *mem;           // 分配的内存块
        int object_size;    // 对象大小
        int count;          // 对象数量
        int free_count;     // 空闲对象数
        void *free_list;    // 空闲链表
    };

    std::vector<Slab> slabs;

public:
    void create_slab(int object_size, int count) {
        Slab s;
        s.mem = malloc(object_size * count);
        s.object_size = object_size;
        s.count = count;
        s.free_count = count;
        // 初始化 free_list(所有对象串起来)
        slabs.push_back(s);
    }

    void* allocate(int object_size) {
        for (auto& slab : slabs) {
            if (slab.object_size == object_size && slab.free_count > 0) {
                void *obj = slab.free_list;
                slab.free_list = *(void**)obj;
                slab.free_count--;
                return obj;
            }
        }
        return nullptr;  // 没有可用的 slab
    }
};

特点:
  - 每个 slab 管理固定大小的对象
  - 批量分配(创建 slab 时)
  - 分配/释放 O(1)
  - 适合已知对象大小的场景

Summary

  • Memory Pool: Pre-allocate large block; on-demand allocation; no system calls; no lock contention
  • Fixed-Size Pool: All objects same size; managed by free list; O(1) alloc/dealloc
  • Performance: 100-500x improvement (vs malloc/free); suitable for high-frequency allocation scenarios
  • Applications: Game servers; network packet processing; real-time systems
  • Slab: One slab per size class; batch allocation; O(1) operations

Next (A08): What are wild pointers and dangling pointers? How are they different from UAF, and how to avoid them.


Last modified: 2024年4月25日

Author

Comments

Write a Reply or Comment

Your email address will not be published.