一个shared_ptr的拷贝,在单线程无竞争的情况下大约 5-8 纳秒(具体数字因 CPU 微架构和频率而异,下文的延迟数据均取典型 x86 服务器芯片的量级)。在 8 个线程同时拷贝同一个对象的shared_ptr时,这个数字可以膨胀到 200 纳秒以上。
40 倍。
不是因为你写了锁,不是因为你搞了什么复杂的同步原语。只是拷贝了一个"线程安全"的智能指针。
这篇文章要做一件事:把shared_ptr引用计数那个"原子操作"的真实代价,一笔一笔拆给你看。不讲泛泛的"原子操作有开销",而是从控制块的内存布局、CPU 的原子指令、缓存一致性协议、内存屏障、再到多线程竞争的放大效应,逐项量化。
先摊开底牌:控制块的内存布局
讨论原子操作的代价之前,先看清楚我们在操作什么东西。
一个shared_ptr<T>的大小是两个裸指针,16 字节(64 位平台)。第一个指向被管理的对象T,第二个指向控制块(control block)。控制块是引用计数的承载体,所有共享同一个对象的shared_ptr和weak_ptr都指向同一个控制块实例。
在 libstdc++(GCC 的标准库实现)里,控制块的核心基类叫_Sp_counted_base,位于<bits/shared_ptr_base.h>。它的关键成员只有两个: