随想1:从make_shared到 std::deque到 protobuf areana模式
随想系列:记录我在项目和学习过程中的思考和联想,不定期更新。
std::make_shared、std::deque 和 protobuf 的 Arena 分配,表面用途各异,但本质都在解决同一个底层问题:避免频繁、离散的小内存分配带来的性能损耗。通用分配器(如 malloc)在高频率、小粒度场景下容易成为瓶颈——不仅分配本身开销大,还会导致内存碎片、缓存局部性差,甚至引发锁竞争。这三种机制通过引入结构或生命周期上的约束,把原本零散的分配请求聚合成更少、更大的操作,从而绕过这些陷阱。

std::make_shared 针对的是智能指针的元数据与对象分离问题。传统 shared_ptr(new T) 会触发两次堆分配:一次给对象,一次给引用计数控制块。这不仅多一次系统调用,还让紧密关联的数据分散在内存中。make_shared 将两者合并到单次分配中,提升缓存亲和性,

std::deque 则重新思考了动态容器的内存布局。它放弃 vector 式的全局连续性,转而采用分段连续结构:内部由多个固定大小的缓冲块组成,通过中央索引表管理。这种设计使得头尾插入/删除无需搬移整个数组,避免了 vector 扩容时的 O(n) 拷贝。虽然随机访问多了一层间接寻址,但每个块内仍保持局部性,且整体分配次数大幅减少——尤其适合频繁在两端操作、又不愿承担重分配成本的场景。

protobuf 的 Arena 分配它假设一批对象具有相同的生命周期(比如一次 RPC 请求中的所有消息),于是预先申请一大块内存,所有对象都从其中线性分配。析构时不需要逐个调用 delete,只需丢弃整个 Arena。这彻底消除了小对象分配的开销和碎片,前提是对象没有非平凡析构逻辑。在高吞吐服务中,这种“批量生、批量死”的模型能显著降低 GC 压力,提升吞吐与延迟稳定性。
感悟:虽然默认的
malloc/new在通用场景下足够可靠,也提供了最大的灵活性,但正是这种“通用性”让它在特定高性能路径上显得笨重。上述三种方案之所以有效,不是因为它们“更快”,而是因为它们主动放弃了通用性,换来了对内存行为的更强控制。当能明确知道对象怎么生、怎么用、怎么死的时候,就可以用更少的分配、更好的局部性、更低的同步开销
更多推荐



所有评论(0)