【问题标题】:What's the performance penalty of weak_ptr?weak_ptr 的性能损失是多少?
【发布时间】:2011-02-14 10:34:32
【问题描述】:

我目前正在为游戏设计一个对象结构,在我的例子中,最自然的组织变成了一棵树。作为智能指针的忠实粉丝,我专门使用shared_ptr's。然而,在这种情况下,树中的子节点需要访问它的父节点(例如——地图上的生物需要能够访问地图数据——因此他们的父节点的数据。

拥有的方向当然是地图拥有它的存在,因此持有指向它们的共享指针。然而,要从一个存在中访问地图数据,我们需要一个指向父级的指针——智能指针的方式是使用一个引用,即weak_ptr

然而,我曾经读到锁定 weak_ptr 是一项昂贵的操作——也许这不再是真的了——但考虑到 weak_ptr 会经常被锁定,我担心这种设计注定要表现不佳。

因此问题:

锁定一个weak_ptr 的性能损失是什么?它有多重要?

【问题讨论】:

  • 我不确定,但我猜它应该大致相当于复制构建shared_ptr的成本。
  • @James - 所以我假设锁定只是分配的引用计数器的读取和复制......
  • @Kornel:这是引用计数的原子增量;它的实现方式是非常特定于平台的(互斥锁将是最坏的情况;在 Windows 上它是使用 InterlockedIncrement 实现的,我确信 Linux 和其他操作系统具有类似的内置原子操作)。
  • @Kornel:肯定会有性能损失。取消引用 shared_ptr 应该与取消引用原始指针一样快,因为这就是它在内部要做的所有事情(每个 shared_ptr 对象都有自己的指针副本)。如果已删除答案中推荐的解决方案适用于您的特定用例,那将为您提供更好的性能(我很惊讶答案已被删除)。
  • 只使用原始指针指向父级,安全高效。

标签: c++ performance boost weak-ptr


【解决方案1】:

使用/取消引用 shared_ptr 几乎就像访问原始 ptr 一样,锁定 weak_ptr 与常规指针访问相比是一个性能“繁重”的操作,因为此代码必须是“线程感知的”才能正常工作如果另一个线程触发释放指针引用的对象的情况。至少,它必须执行某种互锁/原子操作,根据定义,这种操作比常规内存访问要慢得多。

像往常一样,查看正在发生的事情的一种方法是inspect generated code

#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作为引用传递,我们需要加载引用的值,这就是为什么不同之处只是shared_ptr版本的额外加载。

callFuncShared:

callFuncWeak:

通过weak_ptr 调用会产生多出 10 倍的代码,并且充其量只能通过锁定的比较交换,这本身将比取消引用 raw 或 shared_ptr 花费 10 倍以上的 CPU 时间:

只有当共享计数器不为零时,它才能加载指向实际对象的指针并使用它(通过调用对象,或创建shared_ptr)。

【讨论】:

    【解决方案2】:

    对于我自己的项目,我能够通过添加 #define BOOST_DISABLE_THREADS 在任何提升包括之前。 这避免了 weak_ptr::lock 的自旋锁/互斥开销,在我的项目中是 一大瓶颈。由于该项目不是多线程 wrt boost,我可以这样做。

    【讨论】:

      【解决方案3】:

      来自 Boost 1.42 源代码(&lt;boost/shared_ptr/weak_ptr.hpp&gt; 第 155 行):

      shared_ptr<T> lock() const // never throws
      {
          return shared_ptr<element_type>( *this, boost::detail::sp_nothrow_tag() );
      }
      

      所以,James McNellis 的评论是正确的;这是复制构建shared_ptr 的成本。

      【讨论】:

      • 所以我们只有一个引用递增操作?实际上并不奇怪......但是在weak_ptr的情况下有些东西很昂贵 - 知道那是什么吗?我认为相反的(从 shared_ptr 构造一个weak_ptr)也应该是微不足道的......
      • @Kornel Kisielewicz:之前的评论已删除——我一开始以为它说“仅参考实现”哈哈!我对针对weak_ptr 的效率参数的猜测是与内置指针而不是shared_ptrs 的比较(毕竟,你可以有一个内置指针指向与shared_ptr 相同的位置:))
      • 是的,也可能是这样。发布这个问题后我的第二个反应是它可能与线程有关,但似乎弱和共享使用相同的引用计数结构,所以应该没有区别。
      • @James——所以性能下降了——因为有了共享指针,我们只会取消引用它,而不是复制。在weak_ptr的情况下,我们需要该副本和引用增量来使用它,然后在它超出范围后减量。 42 纳秒的 xP 就这么多
      • 看来这个答案是错误的并且具有误导性:所有可能与性能相关的代码实际上都在试图锁定weak_ptr的shared_ptr ctor中。换句话说,它不是复制或构造shared_ptr的副本。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      • 2012-01-04
      • 1970-01-01
      • 2011-02-22
      • 2012-02-08
      • 2018-04-23
      相关资源
      最近更新 更多