std::shared_ptr原子引用计数拖慢性能是因为即使单线程下也无法省略原子指令,导致内存屏障、优化受限及缓存一致性开销;实测tight loop中性能比裸指针低3–5倍。
std::shared_ptr 默认使用原子操作(如 std::atomic_fetch_add)管理引用计数,确保多线程环境下安全。但即使在单线程场景下,编译器也无法省略这些原子指令——它们会强制内存屏障、禁用部分优化,并可能触发锁总线或缓存一致性协议开销。实测中,在 tight loop 里频繁拷贝/析构 std::shared_,性能可能比裸指针低 3–5 倍。
ptr
不能直接“关掉”原子性——std::shared_ptr 标准规定其控制块的引用计数必须是线程安全的。但你可以绕过默认控制块,用 std::shared_ptr 的别名构造 + 自定义删除器来避免原子操作:
struct NonAtomicDeleter {
void operator()(int* p) const noexcept {
delete p;
}
};
// 构造时不经过标准控制块,引用计数不启用原子操作
auto ptr = std::shared_ptr(new int(42), NonAtomicDeleter{});
注意:std::make_shared 无法用于此场景,因为它总会分配带原子计数的控制块;必须用原始指针 + 自定义删除器构造。
shared_ptr 实例的生命周期std::make_shared 创建的同对象 shared_ptr 混用,否则控制块不一致,导致未定义行为shared_ptr 仍会调用原子操作——因为 shared_ptr 的拷贝赋值/构造函数内部仍会访问控制块的引用计数若不需要共享语义,std::shared_ptr 本身就是误用。性能问题只是表象,根源在于设计阶段没厘清所有权:
立即学习“C++免费学习笔记(深入)”;
std::unique_ptr 替代:零运行时开销,移动语义清晰,且能配合 std::move 和 std::make_unique
const T& 或 T*,而非 std::shared_ptr
shared_ptr(如 Boost 的 boost::local_shared_ptr),它用普通整型计数,不依赖原子指令只要出现以下任一情况,就无法规避原子引用计数:
std::shared_ptr
std::weak_ptr —— 它依赖同一控制块中的原子弱引用计数std::shared_ptr::owner_before 或比较操作做排序,因为实现依赖控制块地址和原子状态std::shared_ptr 存入 std::vector 并频繁 resize —— 每次元素移动都触发引用计数增减真正难处理的不是原子操作本身,而是人们把 std::shared_ptr 当作“不用动脑的所有权解决方案”,结果在 hot path 上反复构造/拷贝,却没意识到大部分时候只需要移动或引用。