Performance of raw pointers vs smart pointers in C++11

In this post I analyse and discuss the performance of raw pointers vs smart pointers in few C++11 benchmarks. The operations tested are creation, copy, data access and destruction.





Disclaimer

These are limited tests based on simple code I wrote without considering any kind of optimization or coding / design style. They test exclusively STL smart pointers and I ran them only on my Linux machine using gcc 4.8 to compile them.

Things could be totally different in your program running your code on a different machine.

The Benchmarks

The following benchmarks measure the performance of raw pointers, std::unique_ptr and std::shared_ptr in C++11.

Accessing data

The first benchmark simply calls a function of the pointed object using the following code:

for(int r = 0; r < NUM_REPS; ++r) p->Do();

The code in Do() is pretty simple and it involves only 2 increments, 1 addition and 1 if.

Construction, data access and destruction

For each pointer the code creates a new (dummy) object, performs some operation with it and then destroys it.

The code used to test C++ raw pointers is the following:

for(int r = 0; r < NUM_REPS; ++r) { ActorIncrement * p = new ActorIncrement; p->Do(); val += p->GetVal(); delete p; }

The code used to test C++ std::unique_ptr is the following:

for(int r = 0; r < NUM_REPS; ++r) { std::unique_ptr p(new ActorIncrement); p->Do(); val += p->GetVal(); }

The code used to test C++ std::shared_ptr is the following:

for(int r = 0; r < NUM_REPS; ++r) { std::shared_ptr p(new ActorIncrement); p->Do(); val += p->GetVal(); }

A second test aims at checking the performance of std::make_shared compared to the previous way of creating a shared pointer. The code used for this test is the following:

for(int r = 0; r < NUM_REPS; ++r) { std::shared_ptr p = std::make_shared<ActorIncrement>(); p->Do(); val += p->GetVal(); }

The code in Do() is the same as in the previous benchmark, whereas GetVal() is a simple inline function returning a value.

Copy

A common use of pointers is passing data around, this is tested by the last benchmark.

The code used to test C++ raw pointers is the following:

ActorIncrement * p = new ActorIncrement; for(int r = 0; r < NUM_REPS; ++r) { ActorIncrement * p2 = p; p2->Do(); val += p2->GetVal(); TestRaw(p); TestRaw(p); }

The TestRaw function replicates the first 3 lines of the for cycle and it’s defined like this:

int TestRaw(ActorIncrement * p);

The code used to test C++ std::shared_ptr is the following:

std::shared_ptr p(new ActorIncrement); for(int r = 0; r < NUM_REPS; ++r) { std::shared_ptr p2 = p; p2->Do(); val += p2->GetVal(); val += TestShared(p); val += TestShared2(p); }

The TestShared and TestShared2 functions replicates the first 3 lines of the for cycle and they are defined like this:

int TestShared(const std::shared_ptr & p); int TestShared2(std::shared_ptr p);

Basically the only difference is how the shared pointer is passed to the function (const reference vs value).

Results

Here the results of running the benchmark code a 1,000,000 times (NUM_REPS = 1000000).

The tests were executed on a 64-bit Kubuntu Linux 14.04 machine powered by an Intel i7-4770 CPU @ 3.40GHz and 16Gb of DDR3 RAM. They were compiled using g++ 4.8.4 with the following flags: -O3 -s -Wall -std=c++11.

All the times are in milliseconds, lower values (green) are better.

Accessing data

raw pointer std::unique_ptr std::shared_ptr 5 5 5

As expected there’s no notable difference during normal usage of any pointer.

Construction, data access and destruction

raw pointer std::unique_ptr std::shared_ptr std::make_shared 23 23 46 27

Things get more interesting when considering the whole life of pointers.

As expected an std::shared_ptr is more expensive to use than a raw pointer and that’s because it performs extra operations and allocates extra memory to handle the automatic memory management. It’s important to notice that despite a 100% increase in time, we are still talking about 23ms per 1M pointers. That means that unless your code looks like the one in the benchmark (and it shouldn’t), it’s never going to be a real issue.

It’s also interesting to notice how std::make_shared almost makes the time gap disappear. That’s because it performs a single heap allocation instead of 2 when creating the std::shared_ptr. The time gain comes at a price as an std::shared_ptr created using std::make_shared is kept alive (memory is not cleared) by any instance of an std::weak_ptr which points to the same object, whereas that doesn’t happen when std::share_ptr is created using the new Object syntax.

Copy

raw pointer std::shared_ptr 18 19

Which means there’s no noticeable difference between raw pointers and shared pointers.

Conclusion

It’s fair to say that smart pointers do not create any real performance issue and that they can be safely used over classic raw pointers in pretty much any normal situation.

Subscribe

Don’t forget to subscribe to the blog newsletter to get notified of future posts.

You can also get updates following me on Google+, LinkedIn and Twitter.