高频new[]分配因系统调用和堆管理开销导致cpu占用高、外部碎片;fixedpool将固定长度数组视为大对象,按块分配;需手动构造/析构元素;多线程需无锁设计与线程缓存。

为什么直接用 new[] 分配数组在高频场景下会出问题
因为每次 new[] 都触发系统调用,进入内核态分配堆内存;释放时 delete[] 还要维护堆元数据、合并空闲块。在每秒上万次小数组(比如 int[8]、char[64])分配/释放的场景下,这些开销会吃掉 20%~40% 的 CPU 时间,且很快产生外部碎片——你明明还有 100MB 空闲内存,却连一个连续的 4KB 块都分不出来。
FixedPool 类怎么支持数组语义但不破坏内存池结构
内存池本身不区分“单个对象”和“数组”,它只管按固定大小切分内存块。关键在于:你申请的“数组”必须是固定长度、编译期可知大小的,比如 Vec3[16] 或 PacketHeader[32],而不是运行时才确定长度的 new int[n]。
- 把数组类型整体看作一个“大对象”:例如
sizeof(PacketHeader) * 32就是你要预设的block_size_ - 构造
FixedPool时传入总块数 × 单块字节数,比如FixedPool(1000, sizeof(PacketHeader) * 32) -
allocate()返回的指针可直接 reinterpret_cast 成PacketHeader*,当作数组首地址用 - 释放时仍传原始指针,不需额外长度信息——池只认块起始地址,不关心内部布局
如何避免 placement new 和析构顺序踩坑
如果你的数组元素是带构造/析构的类类型(如 std::string[4]),不能只靠 allocate() 拿内存,必须显式调用构造函数;释放前也得逐个调用析构函数。否则会出现未定义行为或资源泄漏。
- 分配后用
new (ptr) T[4]是错误写法——placement new不支持数组语法 - 正确做法:循环调用
new (ptr + i * sizeof(T)) T()构造每个元素 - 释放前必须反向循环调用
static_cast<t>(ptr)[i].~T()</t>,否则栈对象成员或智能指针不会被清理 - 建议封装成
construct_array<t>(void* ptr, size_t n)</t>和destroy_array<t>(void* ptr, size_t n)</t>辅助函数
多线程下 allocate/deallocate 性能断崖的根源
最简实现里用全局锁保护空闲链表,一旦并发度上去,所有线程都在等一个 mutex,吞吐量反而不如原生 malloc。这不是设计缺陷,而是没做无锁化。
- 别用
std::mutex包裹整个allocate函数体——这是典型性能杀手 - 改用 CAS 操作管理 free list 头指针(
std::atomic<freeblock></freeblock>),使分配/释放变成 lock-free - 每个线程缓存几个空闲块(thread-local cache),减少对全局链表争抢;缓存满/空时再批量交换
- 注意:
deallocate时若目标块属于其他线程的缓存,需安全跨线程归还(可用 MPSC 队列)
真正难的不是写出能跑的内存池,而是让 allocate 和 deallocate 在 16 核机器上压测时保持 sub-10ns 延迟——这要求你亲手控制每一条指令的 cache line 命中、避免 false sharing、把原子操作限制在单个指针上。很多开源实现卡在这一步就退化成“带锁 malloc 封装”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











