Sunday, August 14, 2005

 

Benchmarking Smart Pointers in C++

Before proceeding with the implementation of my Wise Pointers, I decided to create a small benchmark and test some known implementation of reference counted pointer against it. It is far from comprehensive (indeed, is extremely small) but still revealing.
The small program basically does this:
- Creates a std::vector of reference counted pointers to int
- reserves space for N items (see below)
- uses push_back to fill the vector with N items dynamically allocated and pointing to random integers
- calls std::sort to sort the vector, using a dereferencing functor to sort by pointee
- calls std::reverse on the vector
- calls std::random_shuffle on the vector
- sorts the vector again
- calls std::max_element on the vector, using a dereferencing functor to get the max pointee
I then tested the program against:
1) regular (raw) pointers. In this case, the program also loops and delete the dynamically allocated ints at the end
2) my own reference counted smart pointers (implemented using two parallel pointers)
3) boost::shared_ptr (implemented using two nested pointers)
4) Loki::SmartPtr, with the RefCounted policy (implemented using two parallel pointers and a custom small object allocator for the reference count storage)
5) Loki::SmartPtr, with the RefLinked policy (therefore implemented with reference linking)
I run the program with N = 10,000 (ten thousand) and N = 100,000 on a Pentium IV 3.2 GHz, using Visual C++ 7.1 with all the optimization turned on for maximum speed.
Here are the results in clock cycles, very roughly approximated right now:

N = 10,000
1) 26,000
2) 47,000
3) 53,000
4) 47,000
5) 61,000

First interesting conclusions: there is a very significant overhead (although that is not diminishing my love for smart pointers and RAII :-). Parallel pointers are faster than nested pointers as I fully expected :-). The small object allocator in Loki is very disappointing (or, the default allocator for VC 7.1 is very good :-). There is, indeed, a small difference between 2 and 3, but not at this approximation level. Reference linking is slower, as Alexandrescu expected in his "Modern C++ Design" (I have never used that technique in real programs).

N = 100,000
1) 450,000
2) 1,100,000
3) 1,300,000
4) crashes in custom allocator
5) 1,000,000

Relative overhead gets even bigger for 2) and 3), with parallel pointers still a winner. The custom allocator crashes, telling us to keep it simple :-). The performance of the reference linked implementation is quite interesting: it is relatively smaller, although not by much.

Well, as soon as I get some time to implement the Wise Pointers, I'll repeat the benchmark and post the results here.

Comments:
Pensando agli smart pointer mi è venuto in mente questo: boost ha un weak_ptr che può essere usato solo in presenza di shared_ptr. Quando chiesi numi a Doug Gregor (o almeno mi pare fosse lui) mi rispose che aveva senso usare un weak_ptr solo in congiunzione ad un shared_ptr. Io non ero daccordo in quanto ritenevo che weak_ptr dovesse rappresentare più in generale il concetto di "assenza di possesso" mentre in boost è stato inserito solo per eliminare i problemi di riferimenti circolari. Non ho approfondito ma continuo a pensarla in questa maniera (in effetti se ho una factory che ad esempio gestisce il lifetime degli oggetti e internamente memorizza una cache per il riciclo per risparmiare risorse credo sia meglio restituire un weak_ptr che non andrebbe distrutto dal cliente). Tu che ne pensi?
 
Il weak pointer e' un concetto molto generale, che risolve non solo il problema delle referenze circolari ma anche della correttezza formale del modello (oltre ad aumentare di un epsilon l'efficienza in alcuni casi).
Oltre a casi simili a quello che accennavi, ci sono anche altre situazioni, del tipo:
- A possiede in modo forte B
- A passa un riferimento a se stesso a B, tramite interfaccia A', in modo da avere delle callback
- B deve quindi mantenere un riferimento ad A
Poiche' by design B vivra' meno di A, e sempre by design B non ha influenza nel lifecycle di A, che magari e' a sua volta posseduto in modo forte da un altro oggetto ecc, sarebbe scorretto (al di la' del riferimento circolare) usare un pointer diverso dal weak da B verso A.
Quindi concordo, usare il weak pointer solo in unione allo shared pointer e' una scelta di design cosi' limitativa che a mio avviso si puo' dire scorretta.
Nella famiglia di smart pointer che uso io, ovviamente, questi limiti non ci sono :-).
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?