内存池通过预分配固定大小内存块并用链表管理空闲节点,避免系统调用、锁竞争和碎片,在小对象高频分配场景显著提升性能;但仅适用于大小固定、生命周期相近的对象。

为什么直接 new/delete 或 malloc/free 在高频场景下会变慢
频繁调用 malloc 和 free 会导致内核态/用户态切换、堆管理器锁竞争、内存碎片,尤其在小对象高频分配(如网络包解析、事件节点)时,性能下降明显。glibc 的 malloc 对小块内存虽有 fastbin 优化,但无法避免元数据开销和线程局部缓存未命中。
用固定大小块内存池绕过通用堆管理
核心思路:一次性申请一大块内存(如 64KB),按固定大小(如 128 字节)切分成若干 slot,用单向链表维护空闲块。分配时取头结点,释放时插回头部——全程无系统调用、无锁(单线程)或轻量 CAS(多线程)。
-
malloc替代:从空闲链表弹出节点,返回地址;若链表为空,才 fallback 到malloc扩容(可选) -
free替代:将指针转为节点地址,插入空闲链表头部,不调用free - 注意对齐:slot 起始地址需满足
alignof(std::max_align_t),可用std::aligned_alloc或手动偏移调整 - 示例关键结构:
struct PoolNode { PoolNode* next; };
如何支持多线程且避免 false sharing
多个线程共用一个空闲链表会引发 CAS 冲突和缓存行失效。常见做法是每个线程独占一个本地池(thread-local pool),定期与中心池交换块——但更轻量的是用 std::atomic<poolnode></poolnode> + 指针标记位(如最低位作 busy flag)实现 lock-free 链表。
- 避免 false sharing:每个
PoolNode单独占一缓存行(64 字节),用alignas(64)修饰结构体 - 扩容策略:本地池空了,先尝试从中心池批量取 N 个节点(如 16 个),减少争用
- 危险操作:不要在释放时直接写
node->next = head后再 CAS,必须用compare_exchange_weak原子更新整个 head 指针
何时不该用内存池
内存池只适合生命周期相似、大小固定的对象。如果对象大小差异大(如既有 16 字节结构又有 2KB 缓冲区),或存活时间极不均衡(有些秒级释放、有些贯穿进程),强行统一池化反而导致内存浪费或提前耗尽。
立即学习“C++免费学习笔记(深入)”;
- 调试困难:泄漏时无法用
valgrind直接追踪原始malloc调用栈,需在池内记录分配上下文(如__FILE__/__LINE__) - 内存占用刚性:即使长期只用 5% 的池空间,整块内存仍被进程持有,无法归还 OS
- STL 容器不兼容:不能直接把
std::vector的 allocator 换成自定义池,除非重写符合 Allocator concept 的完整接口(C++17 要求更严)
真正要收口的不是“要不要池”,而是“哪一层该池化”——比如在业务层对象构造前做池化,而不是试图替代 operator new 全局重载,后者容易被第三方库绕过且破坏 ABI 稳定性。










