我在整个应用程序中广泛使用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&)
我有点困惑的是析构函数调用的数量小于复制构造函数调用的数量,但总体而言我对相关运行时间的减少感到非常满意。感谢所有人的建议。
答案 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的评论。