why boost:shared_ptr so slower?

C

chris

I've just done a performance test on the shared_ptr compared to the
native pointer case, the results show at lease 3 times slower when the
iteration number > 10000, this is the code snippet:

#include <vector>
#include <iostream>
#include <boost/shared_ptr.hpp>

using namespace std;
using namespace boost;

class Thing
{
public:
Thing()
{
}

void method (void)
{
int i = 5;
}
};

typedef boost::shared_ptr<Thing> ThingPtr;

void processThing(Thing* thing)
{
thing->method();
}

//loop1 and loop2 test shared_ptr in the vector container
void loop1(long long num)
{
vector<ThingPtr> thingPtrs;

for(int i=0; i< num; i++) {
ThingPtr p1(new Thing);
thingPtrs.push_back(p1);
}
}

void loop2(long long num)
{
vector<Thing> thingPtrs;
for(int i=0; i< num; i++) {
Thing thing;
thingPtrs.push_back(thing);
}
thingPtrs.clear();
}

//loop3 and loop4 test shared_ptr in the vector container
void loop3(long long num)
{
for(int i=0; i< num; i++) {
ThingPtr p1(new Thing);
processThing(p1.get());
}
}

void loop4(long long num)
{
for(int i=0; i< num; i++) {
Thing* p1 = new Thing();
processThing(p1);
delete p1;
}
}

The results are the following:
CPU: Intel Core2 Quad CPU Q8200
RAM: 4G
OS: Windows XP SP2
Compiler: Visual Studio 2005

loop1 vs loop2: 100000 times
loop1 elapsed 390 msec
loop2 elapsed 93 msec

loop5 vs loop6: 100000 times
loop5 elapsed 171 msec
loop6 elapsed 78 msec


If we heavily use the boost::shared_ptr in a large project, is that a
big porformance problem?


chris
 
J

joseph cook

chris writes:
a whole bunch of heap churn going on, allocating and deallocating small
blocks of memory, for all the reference counts. Much higher heap memory
fragmentation.

Horrible.

I have to agree, at least partially. the boost::smart_ptr<> is
allocating memory off the heap (twice!), which on some systems might
make it 100X slower or more than passing around a raw pointer.

Joe Cook
 
B

Brian Wood

I always thought that Boost was generally crap. I never understood that
library's popularity. And shared_ptr was it's cream of the crap. shared_ptr
is a structure with two pointers: one to the original object, and a second
pointer to a separately-allocate memory block that stores the reference
count.

I wouldn't say Boost is generally poor quality. Some of the libs
are pretty good and some are less so. One Boost library that I
think highly of is the Boost Intrusive library --
http://www.boost.org/doc/libs/1_39_0/doc/html/intrusive.html


Brian Wood
www.webEbenezer.net
 
I

Ian Collins

Sam said:
I always thought that Boost was generally crap. I never understood that
library's popularity. And shared_ptr was it's cream of the crap.
shared_ptr is a structure with two pointers: one to the original object,
and a second pointer to a separately-allocate memory block that stores
the reference count.

It's a reference counted smart pointer, so what else do you expect?
 
J

James Kanze

It's a reference counted smart pointer, so what else do you
expect?

Performance isn't really the problem---it's obviously easier to
find examples where boost::shared_ptr is slower than a raw
pointer, because it does more. If you need that "more", the
question is whether boost::shared_ptr is more efficient than
other solutions to the same problem you're trying to solve.
From what I've seen, boost::shared_ptr is a excellent
implementation of its design specification, and when used
appropriately, does the job quite well.

What worries me in the original posting is the "we heavily use
the boost::shared_ptr". There aren't really that many cases
where it's appropriate, and the "heavily use" sounds like
they're counting on it to be a silver bullet for all object
lifetime and memory management issues. Which it isn't.
 
M

Maxim Yegorushkin

chris said:
I've just done a performance test on the shared_ptr compared to the
native pointer case, the results show at lease 3 times slower when the
iteration number > 10000, this is the code snippet:

#include <vector>
#include <iostream>
#include <boost/shared_ptr.hpp>

using namespace std;
using namespace boost;

class Thing
{
public:
Thing()
{
}

void method (void)
{
int i = 5;
}
};

typedef boost::shared_ptr<Thing> ThingPtr;

void processThing(Thing* thing)
{
thing->method();
}

//loop1 and loop2 test shared_ptr in the vector container
void loop1(long long num)
{
vector<ThingPtr> thingPtrs;

for(int i=0; i< num; i++) {
ThingPtr p1(new Thing);
thingPtrs.push_back(p1);
}
}

loop1 one body does up to three memory allocations: one in 'new Thing', another
in 'ThingPtr p( said:
void loop2(long long num)
{
vector<Thing> thingPtrs;
for(int i=0; i< num; i++) {
Thing thing;
thingPtrs.push_back(thing);
}
thingPtrs.clear();
}

loop2 does at most one memory allocation in vector::push_back() and a copy of
thing. So, given that Thing small, loop2 always wins.
//loop3 and loop4 test shared_ptr in the vector container
void loop3(long long num)
{
for(int i=0; i< num; i++) {
ThingPtr p1(new Thing);
processThing(p1.get());
}
}

void loop4(long long num)
{
for(int i=0; i< num; i++) {
Thing* p1 = new Thing();
processThing(p1);
delete p1;
}
}

The results are the following:
CPU: Intel Core2 Quad CPU Q8200
RAM: 4G
OS: Windows XP SP2
Compiler: Visual Studio 2005

loop1 vs loop2: 100000 times
loop1 elapsed 390 msec
loop2 elapsed 93 msec

loop5 vs loop6: 100000 times
loop5 elapsed 171 msec
loop6 elapsed 78 msec

Well, you posted results of loop1 vs loop2 which compare quite different things
and loop5 vs. loop6, for which you did not provide any source code. Your
question can not be answered, since there are no relevant facts provided.
 
J

Jorgen Grahn

I always thought that Boost was generally crap. I never understood that
library's popularity. And shared_ptr was it's cream of the crap. shared_ptr
is a structure with two pointers: one to the original object, and a second
pointer to a separately-allocate memory block that stores the reference
count.

So, passing a shared_ptr around, say, as a parameter to a function call,
means pushing two pointers on the stack, versus one. 200% overhead.

Two-instead-of-one is a 100% overhead. Also note that 200% of almost
nothing is still almost nothing, and that far from all function calls
push arguments onto a stack.
Plus,
the reference count is kept in a separate, small object. Each time you
allocate an object, you end up allocating another object, to hold the
refenrece count. Then, with all the shared_ptrs flying around, you now have
a whole bunch of heap churn going on, allocating and deallocating small
blocks of memory, for all the reference counts. Much higher heap memory
fragmentation.

I would have expected them to pool these allocations somehow. But I
haven't read the source code, and more importantly I haven't profiled
boost.shared_ptr against another solution in real code. I have never
needed shared pointers badly enough.

/Jorgen
 
N

Noah Roberts

Pete said:
Whoa, that's mixing a couple of different things.


There is one allocation off the heap when you create an object. There is
one allocation off the heap when you create a boost::shared_ptr object
to manage that object. So you get two allocations when you create an
object and manage it with a boost::shared_ptr, as opposed to one
allocation when you create an object and manage it with a raw pointer.
There are no additional allocations when you pass a boost::shared_ptr
object around.

The speed difference between a raw pointer and a shared_ptr is mostly in
managing the shared_ptr's reference count: increment when you create a
new shared_ptr object, decrement when you destroy it.

Which is a good reason to pass them around as const references and let
the function create a copy if it needs it.
 
N

Noah Roberts

Pete said:
Which pretty much defeats their purpose. If you've measured the overhead
of reference counting and you can't afford it, then you shouldn't be
using it.
There's no reason to make a copy of a shared_ptr unless you actually
need it to be shared. Just as we don't pass strings around willy-nilly
by value, we shouldn't pass shared_ptrs that way either. If the object
being called wants a copy, it will make one.
 
J

Juha Nieminen

Sam said:
So, passing a shared_ptr around, say, as a parameter to a function call,
means pushing two pointers on the stack, versus one. 200% overhead.
Plus, the reference count is kept in a separate, small object. Each time
you allocate an object, you end up allocating another object, to hold
the refenrece count. Then, with all the shared_ptrs flying around, you
now have a whole bunch of heap churn going on, allocating and
deallocating small blocks of memory, for all the reference counts. Much
higher heap memory fragmentation.

Horrible.

Since you don't like how the boost shared_ptr is implemented, it
sounds like you have a better idea. Let's hear it. How would you
implement such a shared_ptr?

Naturally it should have the same basic properties:

- Non-intrusive. We want to be able to use it for existing object types
(and even basic types if so desired).

- Since it's a smart pointer to a shared object, and it should
automatically destroy the object when the last pointer is destroyed, it
has to be reference-counted somehow.

- Works with incomplete types (the only place where the type must be
complete is when constructing the smart pointer).

- Thread-safe.
 
N

Noah Roberts

Pete said:
There's no reason to make a copy of a raw pointer unless you actually
need it to be shared. Except, of course, that passing pointers by
reference is not what people usually do. shared_ptr is supposed to look
like a pointer, not like a string.

Oh, and since you're micro-optimizing, don't forget to measure the
performance impact of passing shared_ptr objects by reference.

Just like you don't prematurely optimize...don't prematurely pessimize.
If there's no reason to pass objects by value...don't do it. This
isn't a micro-optimization issue.
 
I

Ian Collins

Sam said:
Someone to think about it, before cranking out the code. shared_ptr may
be an adequate academical implementation, but it belongs in a classroom,
not in the real world. And part of the classroom study would be how it
fragments the heap, with bazillions of tiny chunks of allocated memory.

Which would lead into the class on heap manager design.
 
A

Alf P. Steinbach

* Pete Becker:
Once again: shared_ptr is supposed to look like a pointer, not like a
string. If you don't pass pointers by reference, don't pass shared_ptr's
by reference.

I'm with Noah in principle, but with you in practice. :) I've never felt the
need for doing const& for shared_ptr. It's just more to write for no particular
(significant) gain, and it may also convey a misleading impression that this
shared_ptr's reference count or deleter can't be modified, but in principle I
think enforcing on oneself a convention of doing const& for all but basic types
could remove some inefficiencies -- it's just that it's not that important.


Cheers,

- Alf
 
S

Squeamizh

Once again: shared_ptr is supposed to look like a pointer, not like a
string. If you don't pass pointers by reference, don't pass shared_ptr's
by reference.

This is not a very convincing argument.
 
K

Keith H Duggar

Since you don't like how the boost shared_ptr is implemented, it
sounds like you have a better idea. Let's hear it. How would you
implement such a shared_ptr?

Naturally it should have the same basic properties:

- Non-intrusive. We want to be able to use it for existing object types
(and even basic types if so desired).

- Since it's a smart pointer to a shared object, and it should
automatically destroy the object when the last pointer is destroyed, it
has to be reference-counted somehow.

- Works with incomplete types (the only place where the type must be
complete is when constructing the smart pointer).

- Thread-safe.

Boost shared_ptr is not "thread safe" by any standard that is
usually meant by the term:

http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htm#ThreadSafety

ie "the same level of thread safety as built-in types" means
not "thread safe". I don't know why this misconception about
boost shared_ptr is so common. Was an earlier version of it
"thread safe" with some lock blah hidden in it? That would
hit the performance even more.

KHD
 
J

Juha Nieminen

Keith said:
Boost shared_ptr is not "thread safe" by any standard that is
usually meant by the term:

http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htm#ThreadSafety

First you claim that it's not thread-safe, and then you point to the
documentation which says that it is.
ie "the same level of thread safety as built-in types" means
not "thread safe".

You are confusing two different types of thread-safety.

Instances of shared_ptr are exactly as thread-safe as, for example,
regular pointers. If you want to pass a regular pointer from one thread
to another, you will need to use some synchronization mechanism in order
for this to work (in other words, for the target thread to know when it
can use the pointer it was given by the source thread). shared_ptr is in
no way different: If you want to pass an instance from one thread to
another, you will need some kind of synchronization, in the exact same
way as with *any* type.

What makes shared_ptr thread-safe compared to other, naive
reference-counting smart pointers, is that if an object is being shared
by more than one thread, the shared_ptr instances inside one thread can
be safely copied, assigned and destroyed without it affecting the
validity of the shared_ptrs in the other thread which point to the same
object.

A naive reference-counting smart pointer implementation which does not
take into account thread-safety cannot be used in this way. There will
be mutual exclusion problems if two threads, which share the same
object, copy, assign or destroy instances of this smart pointer pointing
to that object. That's because they are not only sharing the object,
they are also sharing the reference counter: Changing it must be
thread-locked.
I don't know why this misconception about
boost shared_ptr is so common.

I think it's you who is having a misconception here.
 
J

Juha Nieminen

Sam said:
Understood. Boost is free to implement whatever cockamamie algorithm it
wants. Efficient runtime performance is someone else's problem.

I'm still waiting to see your better solution to this.

If you don't have a better solution to offer, stop bitching.
 
J

joseph cook

Whoa, that's mixing a couple of different things.

There is one allocation off the heap when you create an object. There is
one allocation off the heap when you create a boost::shared_ptr object
to manage that object. So you get two allocations when you create an
object and manage it with a boost::shared_ptr, as opposed to one
allocation when you create an object and manage it with a raw pointer.
There are no additional allocations when you pass a boost::shared_ptr
object around.

The speed difference between a raw pointer and a shared_ptr is mostly in
managing the shared_ptr's reference count: increment when you create a
new shared_ptr object, decrement when you destroy it.

I disagree. In my implementation of Boost, anyway, there are 2 memory
allocations for every shared_ptr creation, not one. Also, the whole
point of shared_ptr is that I can have, many pointers to the same
memory location, and not worry about when it gets deleted.

In a real application, maybe I would create the object once, and
destruct it once. In the course of my application, I could have many
pointers to this data. I want to delete the data when finally no one
is using it (shared_ptr<>). If I were using plain pointers, I would
always have one heap allocation, period. It wouldn't depend on how
many intermediate objects are pointing to this data (sharing it).

As for the comments "why don't you write a better one", that is just
nonsense. I (and many others), have written implementations of
shared_ptr that do not require heap allocations.

Joe Cook
 
K

Keith H Duggar

First you claim that it's not thread-safe, and then you point to the
documentation which says that it is.

No, I pointed to a document that says it is "as thread safe as ..."
not that it is "thread safe".
You are confusing two different types of thread-safety.

No, you are fundamentally missing my point. Obviously I know the
level of "thread safety" that boost::smart_ptr provides (since I
posted the reference) so you should have at least thought for a
a moment about what my point is and at least recognize the point
(whether you agree or not) before launching into an elaboration
of what the document already explains.

My point (since you missed it the first time) is that "as thread
safe as a built-in type" is not, in my opinion, what one commonly
thinks of when someone says a construct is "thread safe". So when
you posted this
Naturally it should have the same basic properties:
...
- Thread-safe.

it can easily be misunderstood or deceive someone without the
qualification "as a built-in type" which the boost document is
careful to include. To put it simply

"as thread-safe as a built-in type" != "thread safe"

and it is deceptive not to qualify it appropriately when you
challenge someone to produce an alternate implementation.
Instances of shared_ptr are exactly as thread-safe as, for example,
regular pointers. If you want to pass a regular pointer from one thread
to another, you will need to use some synchronization mechanism in order
for this to work

Which is exactly why it is not "thread safe" but rather only
"as thread safe as a ...".

[snip further elaboration of already linked documentation]
I think it's you who is having a misconception here.

No, you were just missing my point. I hope it's clearer now.

KHD
 
K

Keith H Duggar

No, I pointed to a document that says it is "as thread safe as ..."
not that it is "thread safe".

By the way, I don't mean this as any kind of argument against boost
shared_ptr. I think the boost implementation strikes the right level
of thread-safety. A fully "thread safe" shared_ptr would not be as
useful (at least to me) as it would (most likely) suffer very high
performance penalties and support a more limited interface. I think
the two most useful levels are undefined-thread-safety (ie no extra
work for safety) and boost-level-thread-safety (for a shared_ptr).

KHD
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top