new/delete在高频小对象场景下拖慢程序,因其每次调用均需经系统堆管理器,引发锁竞争、元数据开销与碎片整理,实际开销远超内存本身。

为什么 new 和 delete 在高频小对象场景下会拖慢程序
因为每次调用 new 都要走系统堆管理器(如 malloc),涉及锁竞争、元数据维护、内存碎片整理。小对象(比如几十字节的节点)反复分配释放时,开销远超实际内存使用本身。
典型现象:压测时 CPU 花在 __libc_malloc 或 je_malloc 里占比奇高;valgrind --tool=massif 显示堆峰值不大但分配次数爆炸;STL 容器(如 std::list<node></node>)插入速度随数量增长明显变缓。
- 不要直接重载全局
operator new—— 它影响所有类型,容易引发第三方库行为异常 - 优先对明确知道生命周期和大小的类定制内存池,比如链表节点、网络包头、事件结构体
- 注意对齐:若类含
double或std::max_align_t成员,内存池分配单元必须按alignof(Node)对齐,否则触发 UB
手写简易定长内存池:避开 std::pmr::memory_resource 的学习成本
如果你只配一个固定大小的池(比如所有 PacketHeader 都是 64 字节),自己管 char* + 自由链表比引入 std::pmr 更轻量、更可控。
关键不是“多线程安全”,而是“不越界、不重复释放、对齐正确”。下面这个模式能覆盖 80% 的嵌入式/游戏/网络中间件场景:
立即学习“C++免费学习笔记(深入)”;
class PacketHeaderPool {
static constexpr size_t kSize = 64;
static constexpr size_t kAlign = alignof(PacketHeader);
char* memory_;
std::vector<void*> free_list_;
<p>public:
PacketHeaderPool(size<em>t cap) : memory</em>(static_cast<char<em>>(aligned_alloc(kAlign, cap </em> kSize))) {
for (size_t i = 0; i < cap; ++i) {
free<em>list</em>.push<em>back(memory</em> + i * kSize);
}
}</p><pre class='brush:php;toolbar:false;'>void* allocate() {
if (free_list_.empty()) return nullptr;
void* p = free_list_.back();
free_list_.pop_back();
return p;
}
void deallocate(void* p) {
free_list_.push_back(p);
}};
-
aligned_alloc是必须的,malloc不保证对齐,尤其在 ARM 或含 SIMD 成员时会 crash - 别在
deallocate里 memset —— 真实业务中往往需要保留部分字段(如 seq_id),清零反而掩盖 bug - 如果池耗尽,返回
nullptr比抛异常更合理:上层可 fallback 到new,避免单点崩溃
用 std::pmr::unsynchronized_pool_resource 时最常踩的三个坑
它不是“开箱即用”的银弹。很多团队启用了却没提速,甚至更慢,问题基本出在这三处:
- 池子太小:
std::pmr::unsynchronized_pool_resource默认只预分配几 KB,高频分配下频繁触发底层upstream(通常是std::pmr::new_delete_resource())申请大块内存 —— 这就又回到原点了 - 误用
polymorphic_allocator泛型容器:std::pmr::vector<int></int>分配的是 int 数组,不是 vector 对象本身;对象生命周期仍由栈或外部管理,池只管元素内存 - 跨线程共享同一个
std::pmr::unsynchronized_pool_resource实例 —— 名字里的unsynchronized就是警告你:它不带锁,多线程同时allocate会破坏内部链表
正确做法:每个线程持有一个独立池实例,或改用 std::pmr::synchronized_pool_resource(但要注意锁粒度)。
怎么验证内存池真起作用了
别只看跑分。真实收益要看三件事:分配延迟分布、系统调用次数、实际内存占用。
- 用
perf record -e syscalls:sys_enter_mmap,syscalls:sys_enter_brk ./your_app确认 mmap/brk 调用次数是否大幅下降 - 在关键路径加
std::chrono::high_resolution_clock测pool.allocate()vsnew单次耗时,注意排除第一次分配的 page fault 影响 - 检查
/proc/[pid]/maps:用了池之后,堆段([heap])增长应显著放缓,而你的池内存(如果是mmap分配)会以独立匿名映射出现
最容易被忽略的是:池内存没被复用。比如每次请求都从池取新块,但从不 deallocate 回去,或者 deallocate 了但后续 allocate 没命中自由链表 —— 这种“伪池化”只会让内存占用更高。











