为何libc++ shared_ptr未拆分acq_rel fetch_sub为release操作加acquire屏障
首先明确:你提到的Boost版本实现从C++内存模型的正确性上是完全成立的。
它的逻辑是:除了最后一个持有引用的线程外,其他线程调用release()只需要通过release语义的递减,保证自己对共享对象的所有修改能正确发布出去;只有当线程发现自己是最后一个引用持有者(递减后引用计数归0)时,才需要插入acquire屏障,同步之前所有线程发布的修改,之后再安全析构对象。从理论上看,这个写法把acquire语义的开销从所有调用路径挪到了只会走一次的析构路径,看起来确实更高效。
libc++没有采用这个写法,核心是实际工程层面的权衡,和正确性无关:
- 首先,在当前主流硬件上,两种写法的非析构路径开销几乎没有差别。x86是强内存序架构,不管你给原子读改写操作指定acquire、release还是acq_rel语义,编译出来的都是带lock前缀的原子指令,不存在额外开销;ARMv8、RISC-V等现代弱内存序架构,原子的读改写指令本身就支持直接附带acquire/release语义标记,硬件会动态跳过不需要的同步约束——非析构路径下,acq_rel的fetch_sub比纯release的fetch_sub多出来的acquire标记,因为后续没有需要依赖这个同步结果的内存访问,硬件会直接消掉这部分开销,根本不会比纯release版本慢。
- 其次,独立的acquire屏障实际开销可能更高。很多人忽略了一点:编译器对作用域全局的
atomic_thread_fence的优化保守度,远高于原子操作自带的内存序标记。独立acquire屏障会约束屏障前后所有内存操作的重排,会大幅限制编译器指令调度的空间;而原子操作自带的acquire语义,只会约束当前原子操作和后续依赖该原子结果的内存访问的顺序,优化灵活度高得多。社区早年做过对应的微基准测试,在不少平台上,直接用acq_rel的fetch_sub的版本,实际跑出来比release递减+分支内加acquire屏障的版本还要快一点。 - 最后,收益完全覆盖不了维护成本。shared_ptr的
release()走到析构路径的时候,本身要执行对象析构、弱引用计数处理、自定义deleter调用、内存块回收等一系列重开销操作,内存序带来的那点理论开销占比几乎可以忽略。为了这点几乎测不出来的收益,换成拆分写法反而要额外审计不同编译器版本、不同硬件架构下的屏障生成逻辑,增加后续维护的风险,完全不划算。
补充一点:LLVM社区早年确实收到过把release()换成Boost写法的优化patch,最后全平台性能测试跑下来没有可观测的性能提升,在部分老ARM架构上因为acquire屏障会生成单独的全系统dmb指令,性能反而略有回退,所以最终就保留了现在直接用memory_order_acq_rel做fetch_sub的实现。
内容的提问来源于stack exchange,提问作者Andriy Tylychko
相关产品推荐
相关产品推荐

