我目前正在为游戏设计一个对象结构,在我的例子中最自然的组织变成了一棵树。作为智能指针的忠实粉丝,我专门使用
shared_ptr
。然而,在这种情况下,树中的子级将需要访问其父级(例如,地图上的生物需要能够访问地图数据,因此需要访问其父级的数据。
拥有的方向当然是地图拥有它的存在,因此保存指向它们的共享指针。然而,要从存在内部访问地图数据,我们需要一个指向父级的指针——智能指针方式是使用引用,因此
weak_ptr
。
然而,我曾经读到,锁定
weak_ptr
是一项昂贵的操作——也许这不再是事实了——但考虑到 weak_ptr
会经常被锁定,我担心这种设计注定性能不佳.
因此问题:
锁定weak_ptr 的性能损失是什么?有多重要?
来自 Boost 1.42 源代码(
<boost/shared_ptr/weak_ptr.hpp>
第 155 行):
shared_ptr<T> lock() const // never throws
{
return shared_ptr<element_type>( *this, boost::detail::sp_nothrow_tag() );
}
因此,詹姆斯·麦克内利斯的评论是正确的;这是复制构造
shared_ptr
的成本。
对于我自己的项目,我能够通过添加
#define BOOST_DISABLE_THREADS
在任何提升之前包括。
这避免了weak_ptr::lock的自旋锁/互斥开销,在我的项目中是
一个主要瓶颈。由于该项目不是多线程 wrt boost,我可以这样做。
使用/取消引用 shared_ptr 几乎就像访问原始 ptr,锁定 weak_ptr 与常规指针访问相比是一个性能“重”操作,因为此代码必须是“线程感知”才能正常工作,以防万一另一个线程触发指针引用的对象的释放。至少,它必须执行某种互锁/原子操作,根据定义,该操作比常规内存访问慢得多。
像往常一样,查看正在发生的情况的一种方法是检查生成的代码:
#include <memory>
class Test
{
public:
void test();
};
void callFuncShared(std::shared_ptr<Test>& ptr)
{
if (ptr)
ptr->test();
}
void callFuncWeak(std::weak_ptr<Test>& ptr)
{
if (auto p = ptr.lock())
p->test();
}
void callFuncRaw(Test* ptr)
{
if (ptr)
ptr->test();
}
通过shared_ptr和裸指针访问几乎是一样的。
shared_ptr
需要先加载参考值,比原始版本需要额外加载一次。
callFuncShared:
调用FuncRaw:
callFuncWeak:
通过
weak_ptr
调用会产生 10 倍以上的代码,并且最多必须经过锁定的比较交换,这本身将比取消引用 raw 或 shared_ptr 花费 10 倍以上的 CPU 时间:
只有当共享计数器不为零时,它才能加载指向实际对象的指针并使用它(通过调用该对象,或创建一个
shared_ptr
)。