Boost logo

Boost :

From: Marcin Kalicinski (kalita_at_[hidden])
Date: 2005-04-03 14:27:22


>You can do this with shared_ptr/weak_ptr.

You are right. I discovered that some time ago, but found this solution more
cumbersome to use compared to the one I already had (live_ptr). With
shared_ptr/weak_ptr you have to:
- add a data member of type shared_ptr
- remember to initialize it in all constructors
- add way to obtain weak_ptrs from the stored shared_ptr - probably by
adding an extra member function

Last but not least, despite of all the optimization effort people put into
these classes, shared_ptr/weak_ptr combination is quite slow for that task.
I have run several tests with full optimization enabled in Visual Studio C++
2003, P4 2.4GHz:

[live_ptr | shared_ptr/weak_ptr]
Single threaded: 0.921s | 1.891s (live_ptr is 2x faster)
Multi threaded: 1.015s | 8.063s (live_ptr is 8x faster)

Below is source code for that test:

#include <iostream>
#include <ctime>
#include <vector>
#include <boost/weak_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include "live_ptr.hpp"

using namespace std;
using namespace boost;

// Class for testing live_ptr performance
class A: public live_ptr_target<A>
{
public:
  live_ptr<A> r1, r2, r3, r4;
};

// Class for testing shared_ptr/weak_ptr performance
class B
{
  shared_ptr<B> sp;
public:
  B() { sp.reset(this); }
  weak_ptr<B> get_ref() { return weak_ptr<B>(sp); }
  void destroy() { sp.reset(); }
  weak_ptr<B> r1, r2, r3, r4;
};

void testA()
{
  vector<A *> v;
  for (int i = 0; i < 1000; ++i) // Create objects
    v.push_back(new A);
  for (int i = 0; i < 1000; ++i) // Initialize live references
  {
    A *a = v[i];
    a->r1 = v[i % 8];
    a->r2 = v[i % 32];
    a->r3 = v[i % 128];
    a->r4 = v[i % 512];
  }
  for (int i = 0; i < 1000; ++i) // Juggle live references
  {
    A *a = v[i];
    a->r1 = a->r2;
    a->r2 = a->r3;
    a->r3 = a->r4;
    a->r4 = a->r1;
  }
  for (int i = 0; i < 1000; ++i) // Delete the objects
    delete v[i];
}

void testB()
{
  vector<B *> v;
  for (int i = 0; i < 1000; ++i) // Create objects
    v.push_back(new B);
  for (int i = 0; i < 1000; ++i) // Initialize live references
  {
    B *b = v[i];
    b->r1 = v[i % 8]->get_ref();
    b->r2 = v[i % 32]->get_ref();
    b->r3 = v[i % 128]->get_ref();
    b->r4 = v[i % 512]->get_ref();
  }
  for (int i = 0; i < 1000; ++i) // Juggle live references
  {
    B *b = v[i];
    b->r1 = b->r2;
    b->r2 = b->r3;
    b->r3 = b->r4;
    b->r4 = b->r1;
  }
  for (int i = 0; i < 1000; ++i) // Delete the objects
    v[i]->destroy();
}

int main()
{
  clock_t t1 = clock();
  for (int i = 0; i < 1000; ++i)
    test1();
  clock_t t2 = clock();
  cout << "T1: " << double(t2 - t1) / CLOCKS_PER_SEC << endl;
  t1 = clock();
  for (int i = 0; i < 1000; ++i)
    test2();
  t2 = clock();
  cout << "T2: " << double(t2 - t1) / CLOCKS_PER_SEC << endl;
  return 0;
}

>The problem with providing such an interface is lack of reentrancy and
>thread safety. Consider this code:

That is a valid remark, I didn't think about it before. My applications
never used live_ptrs across threads. I see several solutions to that
problem:

1. provide locking interface (as in weak_ptr) instead of direct access
2. leave is at it is, and let user deal with synchronization, if any is
needed (not every class must be thread safe)
3. provide two separate classes, one implementing 1. and the other 2.

>Your live_ptr<> requires support from Tank, right?

Yes, current implementation requires a class to inherit from
live_ptr_target<T>.

But I understand that being 2x faster and a little more straightforward to
use might not be enough to mandate addition to boost.

cheers,
Marcin Kalicinski


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk