传递shared_ptr的成本

时间:2010-03-23 18:03:59

标签: c++ performance shared-ptr

我在整个应用程序中广泛使用std :: tr1 :: shared_ptr。这包括在函数参数中传递对象。请考虑以下事项:

class Dataset {...}

void f( shared_ptr< Dataset const > pds ) {...}
void g( shared_ptr< Dataset const > pds ) {...}
...

虽然通过shared_ptr传递数据集对象可以保证它在f和g中的存在,但是这些函数可能会被调用数百万次,这会导致很多shared_ptr对象被创建和销毁。这是最近一次运行中平坦gprof配置文件的片段:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls   s/call   s/call  name
  9.74    295.39    35.12 2451177304     0.00     0.00  std::tr1::__shared_count::__shared_count(std::tr1::__shared_count const&)
  8.03    324.34    28.95 2451252116     0.00     0.00  std::tr1::__shared_count::~__shared_count()

因此,大约17%的运行时花在使用shared_ptr对象的引用计数上。这是正常的吗?

我的应用程序的很大一部分是单线程的,我正在考虑重写一些函数

void f( const Dataset& ds ) {...}

并替换了电话

shared_ptr< Dataset > pds( new Dataset(...) );
f( pds );

f( *pds );

在我确定知道对象不会被破坏的地方,当程序流程在f()内时。但是在我开始改变一堆函数签名/调用之前,我想知道传递shared_ptr的典型性能是什么。似乎shared_ptr不应该用于经常被调用的函数。

任何输入都将不胜感激。谢谢你的阅读。

-Artem

更新:在将一些功能更改为接受const Dataset&后,新配置文件如下所示:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls   s/call   s/call  name
  0.15    241.62     0.37 24981902     0.00     0.00  std::tr1::__shared_count::~__shared_count()
  0.12    241.91     0.30 28342376     0.00     0.00  std::tr1::__shared_count::__shared_count(std::tr1::__shared_count const&)

我有点困惑的是析构函数调用的数量小于复制构造函数调用的数量,但总体而言我对相关运行时间的减少感到非常满意。感谢所有人的建议。

5 个答案:

答案 0 :(得分:56)

始终通过 const 引用传递shared_ptr

void f(const shared_ptr<Dataset const>& pds) {...} 
void g(const shared_ptr<Dataset const>& pds) {...} 

编辑:关于他人提到的安全问题:

  • 在整个应用程序中大量使用shared_ptr时,按值传递将花费大量时间(我已经看到它达到50 +%)。
  • 当参数不为空时,请使用const T&而不是const shared_ptr<T const>&
  • 当性能问题时,使用const shared_ptr<T const>&const T*更安全。

答案 1 :(得分:10)

您只需将shared_ptr传递给函数/对象,以备将来使用。例如,某些类可以保留shared_ptr以在工作线程中使用。对于简单的同步调用,使用普通指针或引用就足够了。 shared_ptr不应该完全使用普通指针替换。

答案 2 :(得分:5)

如果你没有使用make_shared,你可以放手一搏吗?通过将引用计数和对象定位在相同的内存区域中,您可以 查看与缓存一致性相关的性能增益。值得一试。

答案 3 :(得分:3)

在性能关键型应用程序中应避免任何对象创建和销毁,尤其是冗余对象的创建和销毁。

考虑一下shared_ptr正在做什么。它不仅创建了一个新对象并填充它,而且它还引用了共享状态来增加引用信息,并且对象本身可能完全存在于其他地方,这将在缓存中变得噩梦。

大概你需要shared_ptr(因为如果你可以逃脱一个本地对象,你就不会从堆中分配一个),但你甚至可以“缓存”shared_ptr取消引用的结果:

void fn(shared_ptr< Dataset > pds)
{
   Dataset& ds = *pds;

   for (i = 0; i < 1000; ++i)
   {
      f(ds);
      g(ds);
   }
}

...因为即使* pds也需要比绝对必要的更多内存。

答案 4 :(得分:1)

听起来你真的知道自己在做什么。您已经分析了应用程序,并且确切地知道了正在使用的循环。您了解只有在不断地执行此操作时,将构造函数调用到引用计数指针才是昂贵的。

我能给你的唯一抬头是:假设你在函数f(t * ptr)里面,如果你调用另一个使用共享指针的函数,你做其他函数(ptr)和其他函数生成一个原始指针的共享指针。当第二个共享指针的引用计数达到0时,您已经有效地删除了您的对象....即使您不想这样做。你说你经常使用引用计数指针,所以你必须注意这样的角点情况。

<击>编辑: 您可以将析构函数设置为私有,并且只使用共享指针类的朋友,这样析构函数只能由共享指针调用,这样您才能安全。 不会阻止共享指针的多次删除。根据Mat的评论。

相关问题