I use std::tr1::shared_ptr extensively throughout my application. This includes passing objects in as function arguments. Consider the following:
class Dataset {...} void f( shared_ptr< Dataset const > pds ) {...} void g( shared_ptr< Dataset const > pds ) {...} ...
While passing a dataset object around via shared_ptr guarantees its existence inside f and g, the functions may be called millions of times, which causes a lot of shared_ptr objects being created and destroyed. Here's a snippet of the flat gprof profile from a recent run:
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()
So, ~17% of the runtime was spent on reference counting with shared_ptr objects. Is this normal?
A large portion of my application is single-threaded and I was thinking about re-writing some of the functions as
void f( const Dataset& ds ) {...}
and replacing the calls
shared_ptr< Dataset > pds( new Dataset(...) ); f( pds );
with
f( *pds );
in places where I know for sure the object will not get destroyed while the flow of the program is inside f(). But before I run off to change a bunch of function signatures / calls, I wanted to know what the typical performance hit of passing by shared_ptr was. Seems like shared_ptr should not be used for functions that get called very often.
Any input would be appreciated. Thanks for reading.
-Artem
Update: After changing a handful of functions to accept const Dataset&
, the new profile looks like this:
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&)
I'm a little puzzled by the number of destructor calls being smaller than the number of copy constructor calls, but overall I'm very pleased with the decrease in the associated run-time. Thanks to all for their advice.
std::shared_ptr:All operations, that do not change the reference count, are as lock-free as the corresponding operations on a raw pointer.
The smart pointer has an internal counter which is decreased each time that a std::shared_ptr , pointing to the same resource, goes out of scope – this technique is called reference counting. When the last shared pointer is destroyed, the counter goes to zero, and the memory is deallocated.
An object referenced by the contained raw pointer will not be destroyed until reference count is greater than zero i.e. until all copies of shared_ptr have been deleted. So, we should use shared_ptr when we want to assign one raw pointer to multiple owners. // referring to the same managed object.
In short: Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.
Always pass your shared_ptr
by const reference:
void f(const shared_ptr<Dataset const>& pds) {...} void g(const shared_ptr<Dataset const>& pds) {...}
Edit: Regarding the safety issues mentioned by others:
shared_ptr
heavily throughout an application, passing by value will take up a tremendous amount of time (I've seen it go 50+%).const T&
instead of const shared_ptr<T const>&
when the argument shall not be null.const shared_ptr<T const>&
is safer than const T*
when performance is an issue.You need shared_ptr only to pass it to functions/objects which keep it for future use. For example, some class may keep shared_ptr for using in an worker thread. For simple synchronous calls it's quite enough to use plain pointer or reference. shared_ptr should not replace using plain pointers completely.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With