How much is the overhead of smart pointers compared to normal pointers in C++?
How much is the overhead of smart pointers compared to normal pointers in C++11? In other words, is my code going to be slower if I use smart pointers, and if so, how much slower?
Specifically, I'm asking about the C++11 std::shared_ptr
and std::unique_ptr
.
Obviously, the stuff pushed down the stack is going to be larger (at least I think so), because a smart pointer also needs to store its internal state (reference count, etc), the question really is, how much is this going to affect my performance, if at all?
For example, I return a smart pointer from a function instead of a normal pointer:
std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();
Or, for example, when one of my functions accept a smart pointer as parameter instead of a normal pointer:
void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);
Solution 1:
std::unique_ptr
has memory overhead only if you provide it with some non-trivial deleter.
std::shared_ptr
always has memory overhead for reference counter, though it is very small.
std::unique_ptr
has time overhead only during constructor (if it has to copy the provided deleter and/or null-initialize the pointer) and during destructor (to destroy the owned object).
std::shared_ptr
has time overhead in constructor (to create the reference counter), in destructor (to decrement the reference counter and possibly destroy the object) and in assignment operator (to increment the reference counter). Due to thread-safety guarantees of std::shared_ptr
, these increments/decrements are atomic, thus adding some more overhead.
Note that none of them has time overhead in dereferencing (in getting the reference to owned object), while this operation seems to be the most common for pointers.
To sum up, there is some overhead, but it shouldn't make the code slow unless you continuously create and destroy smart pointers.
Solution 2:
My answer is different from the others and i really wonder if they ever profiled code.
shared_ptr has a significant overhead for creation because of it's memory allocation for the control block (which keeps the ref counter and a pointer list to all weak references). It has also a huge memory overhead because of this and the fact that std::shared_ptr is always a 2 pointer tuple (one to the object, one to the control block).
If you pass a shared_pointer to a function as a value parameter then it will be at least 10 times slower then a normal call and create lots of codes in the code segment for the stack unwinding. If you pass it by reference you get an additional indirection which can be also pretty worse in terms of performance.
Thats why you should not do this unless the function is really involved in ownership management. Otherwise use "shared_ptr.get()". It is not designed to make sure your object isn't killed during a normal function call.
If you go mad and use shared_ptr on small objects like an abstract syntax tree in a compiler or on small nodes in any other graph structure you will see a huge perfomance drop and a huge memory increase. I have seen a parser system which was rewritten soon after C++14 hit the market and before the programmer learned to use smart pointers correctly. The rewrite was a magnitude slower then the old code.
It is not a silver bullet and raw pointers aren't bad by definition either. Bad programmers are bad and bad design is bad. Design with care, design with clear ownership in mind and try to use the shared_ptr mostly on the subsystem API boundary.
If you want to learn more you can watch Nicolai M. Josuttis good talk about "The Real Price of Shared Pointers in C++" https://vimeo.com/131189627
It goes deep into the implementation details and CPU architecture for write barriers, atomic locks etc. once listening you will never talk about this feature being cheap. If you just want a proof of the magnitude slower, skip the first 48 minutes and watch him running example code which runs upto 180 times slower (compiled with -O3) when using shared pointer everywhere.
EDITED:
And if you ask about "std::unique_ptr" than visit this talk "CppCon 2019: Chandler Carruth “There Are No Zero-cost Abstractions” https://www.youtube.com/watch?v=rHIkrotSwcc
Its just not true, that unique_ptr is 100% cost free.
OFFTOPIC:
I tried to educate people about the the false idea that using exceptions that are not thrown has no cost penalty for over two decades now. In this case it's in the optimizer and the code size.
Solution 3:
As with all code performance, the only really reliable means to obtain hard information is to measure and/or inspect machine code.
That said, simple reasoning says that
You can expect some overhead in debug builds, since e.g.
operator->
must be executed as a function call so that you can step into it (this is in turn due to general lack of support for marking classes and functions as non-debug).For
shared_ptr
you can expect some overhead in initial creation, since that involves dynamic allocation of a control block, and dynamic allocation is very much slower than any other basic operation in C++ (do usemake_shared
when practically possible, to minimize that overhead).Also for
shared_ptr
there is some minimal overhead in maintaining a reference count, e.g. when passing ashared_ptr
by value, but there's no such overhead forunique_ptr
.
Keeping the first point above in mind, when you measure, do that both for debug and release builds.
The international C++ standardization committee has published a technical report on performance, but this was in 2006, before unique_ptr
and shared_ptr
were added to the standard library. Still, smart pointers were old hat at that point, so the report considered also that. Quoting the relevant part:
“if accessing a value through a trivial smart pointer is significantly slower than accessing it through an ordinary pointer, the compiler is inefficiently handling the abstraction. In the past, most compilers had significant abstraction penalties and several current compilers still do. However, at least two compilers have been reported to have abstraction penalties below 1% and another a penalty of 3%, so eliminating this kind of overhead is well within the state of the art”
As an informed guess, the “well within the state of the art” has been achieved with the most popular compilers today, as of early 2014.