2011-07-06 10 views
8

Estoy trabajando en un programa multiproceso, pero tengo un componente UI que hace un uso extensivo de std :: shared_ptr para administrar elementos. Puedo garantizar que solo un hilo usará estos shared_ptrs.Creando un archivo seguro sin hilos shared_ptr

¿Hay alguna manera de definir un shared_ptr que no incurra en la sobrecarga del conteo de referencias seguro para hilos?

Podría estar basado en boost :: shared_ptr o std :: shared_ptr.

EDITAR: Gracias por las respuestas que mencionan intrusive_ptr. Me olvidé de mencionar que también necesito la funcionalidad weak_ptr así que eso lo descarta.

+3

¿es realmente tan malo el gasto general? – Anycorn

+2

De acuerdo. La sobrecarga es tan mínima que no debería ser el cuello de botella. Si tienes un cuello de botella en alguna parte, no está en shared_ptr. – inestical

+1

@inestical: para este caso, probablemente tengas razón, pero no olvides que shared_ptr asigna su recuento en el montón. Hacer eso un millón de veces puede causar gastos generales serios. Por ejemplo, http://stackoverflow.com/questions/3628081/shared-ptr-horrible-speed – stijn

Respuesta

3

puede usar intrusive_ptr, ya que le permite proporcionar su propio recuento de referencias. Si ese conteo de referencia es un incremento/decremento simple de una variable, probablemente no obtendrá un mejor rendimiento que eso.

+0

Gracias, pero también necesito la funcionalidad de weak_ptr. – mpipe3

1

Tengo un código donde la sobrecarga de copiar shared_ptr se ha convertido en un problema, y ​​he utilizado técnicas alternativas en ese momento. Permítanme primero calificar que otros comentarios son correctos de que la sobrecarga de un shared_ptr es muy baja. Hice un perfil de esto para encontrar realmente uno de mis puntos problemáticos. En mi AMD64, llamar a una función de Phenom que copia el shared_ptr toma alrededor de 12ns versus llamar a la misma función con un puntero normal de alrededor de 1ns.

Teniendo en cuenta estos números, es difícil imaginar que obtendrá algún tipo de variante "segura" entre un puntero sin formato y el shared_ptr. Entonces, qué hago en este caso, o paso el puntero real o const & al shared_ptr. Normalmente pongo un bloqueo mutex en toda la sección del código para garantizar la manutención de shared_ptr durante todo el tiempo. Usted podría rodar manualmente un recuento de referencias de un solo subproceso, pero ¿cuál sería el punto si usted sabe que no se está compartiendo?

Pero tenga en cuenta los tiempos con mucho cuidado. A menos que esté copiando shared_ptr miles, o incluso decenas de miles de veces por segundo, no notará la sobrecarga de shared_ptr.

En el código de la GUI en el mismo proyecto siempre utilizo un shared_ptr, solo el código del servidor lo evita en algunas áreas clave. Hay muchas otras cosas en la GUI que lo ralentizan: evitar shared_ptr no supondría una diferencia apreciable.

+0

Sí, tengo una situación donde miles de shared_ptrs pueden copiarse, por lo tanto, con el interés de eliminar la seguridad de subprocesos – mpipe3

1

Sugiero ir con el puntero inteligente intrusivo Boost.

También hay una aplicación de Scott Meyer (aquí: http://www.aristeia.com/BookErrata/M29Source.html) que se publica en 'More Effective C++'

Sin embargo, en caso de que ayuda, me tiró un simple puntero refcounting (con algún apoyo para las asignaciones polimórficos y deletors personalizados). Este se decidió desentierrar-aware.

Nota: he olvidado eso. Las asignaciones polimórficas estaban en una variación para otro proyecto. Eso también lo tengo, pero no es compatible con el eliminador personalizado :) Avíseme si alguien está interesado; Por supuesto se trata con pruebas unitarias separadas para la función

Viene con pruebas unitarias (por ejemplo, para comprobar si hay el famoso bug remove linked list node pedido).Así que ya saben lo que se obtiene :)

/* 
* counted_ptr - simple reference counted pointer. 
* 
* The is a non-intrusive implementation that allocates an additional 
* int and pointer for every counted object. 
*/ 
#ifndef COUNTED_PTR_H 
#define COUNTED_PTR_H 

#include <stdlib.h> 

extern "C" bool mtx_unit_test_countedptr(); 

namespace MtxChess { 

/* For ANSI-challenged compilers, you may want to #define 
* NO_MEMBER_TEMPLATES or explicit */ 

template <class X> 
    struct FreeMallocPolicy 
    { 
     static void do_free(X* p) { if (p) ::free(p); p = 0; } 
    }; 

template <class X> 
    struct ScalarDeletePolicy 
    { 
     static void do_free(X* p) { if (p) delete p; p = 0; } 
    }; 

template <class X> 
    struct ArrayDeletePolicy 
    { 
     static void do_free(X* p) { if (p) delete[] p; p = 0; } 
    }; 

template <class X,class _P=ScalarDeletePolicy<X> > class counted_ptr 
{ 
public: 
    typedef X element_type; 

    explicit counted_ptr(X* p = 0) // allocate a new counter 
     : itsCounter(0) {if (p) itsCounter = new counter(p);} 
    ~counted_ptr() 
     {release();} 
    counted_ptr(const counted_ptr& r) throw() 
     {acquire(r.itsCounter);} 
    operator bool() const { return 0!=get(); } 
    void clear() { (*this) = counted_ptr<X>(0); } 
    counted_ptr& operator=(const counted_ptr& r) 
    { 
     if (this != &r) { 
      auto_release keep(itsCounter); 
      acquire(r.itsCounter); 
     } 
     return *this; 
    } 
    bool operator<(const counted_ptr& r) const 
    { 
     return get()<r.get(); 
    } 
    bool operator==(const counted_ptr& r) const 
    { 
     return get()==r.get(); 
    } 
    bool operator!=(const counted_ptr& r) const 
    { 
     return get()!=r.get(); 
    } 

#ifndef NO_MEMBER_TEMPLATES 
// template <class Y> friend class counted_ptr<Y>; 
    template <class Y> counted_ptr(const counted_ptr<Y>& r) throw() 
     {acquire(r.itsCounter);} 
    template <class Y> counted_ptr& operator=(const counted_ptr<Y>& r) 
    { 
     if (this != &r) { 
      auto_release keep(itsCounter); 
      acquire(r.itsCounter); 
     } 
     return *this; 
    } 
    template <class Y> bool operator<(const counted_ptr<Y>& r) const 
    { 
     return get()<r.get(); 
    } 
    template <class Y> bool operator==(const counted_ptr<Y>& r) const 
    { 
     return get()==r.get(); 
    } 
    template <class Y> bool operator!=(const counted_ptr<Y>& r) const 
    { 
     return get()!=r.get(); 
    } 
#endif // NO_MEMBER_TEMPLATES 

    X& operator*() const throw() {return *itsCounter->ptr;} 
    X* operator->() const throw() {return itsCounter->ptr;} 
    X* get()  const throw() {return itsCounter ? itsCounter->ptr : 0;} 
    bool unique() const throw() 
     {return (itsCounter ? itsCounter->count == 1 : true);} 

private: 
    struct counter { 
     counter(X* p = 0, unsigned c = 1) : ptr(p), count(c) {} 
     X*   ptr; 
     unsigned count; 
    }* itsCounter; 

    void acquire(counter* c) throw() 
    { 
     // increment the count 
     itsCounter = c; 
     if (c) ++c->count; 
    } 

    void release() 
    { 
     dorelease(itsCounter); 
    } 

    struct auto_release 
    { 
     auto_release(counter* c) : _c(c) {} 
     ~auto_release() { dorelease(_c); } 
     counter* _c; 
    }; 

    void static dorelease(counter* itsCounter) 
    { 
     // decrement the count, delete if it is 0 
     if (itsCounter) { 
      if (--itsCounter->count == 0) { 
       _P::do_free(itsCounter->ptr); 
       delete itsCounter; 
      } 
      itsCounter = 0; 
     } 
    } 
}; 

} // EON 

#endif // COUNTED_PTR_H 

Las pruebas unitarias (compila como autónomo)

/* 
* counted_ptr (cpp) - simple reference counted pointer. 
* 
* The is a non-intrusive implementation that allocates an additional 
* int and pointer for every counted object. 
*/ 

#include "counted_ptr.hpp" 
#include "internal.hpp" 
#include <map> 
#include <string> 

namespace MtxChess { 

    namespace /*anon*/ 
    { 
     // sensed events 
     typedef std::map<std::string, int> Events; 
     static Events constructions, destructions; 

     struct Trackable 
     { 
      Trackable(const std::string& id) : _id(id) { constructions[_id]++; } 
      ~Trackable()        { destructions[_id]++; } 
      const std::string _id; 
     }; 

     typedef counted_ptr<Trackable> target_t; 

     bool testBehaviour() 
     { 
      static const counted_ptr<Trackable> Nil = target_t(0); 
      bool ok = true; 

      constructions.clear(); 
      destructions.clear(); 

      MTXASSERT_EQ(ok, 0ul, constructions.size()); 
      MTXASSERT_EQ(ok, 0ul, destructions.size()); 

      target_t a = target_t(new Trackable("aap")); 

      MTXASSERT_EQ(ok, 1ul, constructions.size()); 
      MTXASSERT_EQ(ok, 1, constructions["aap"]); 
      MTXASSERT_EQ(ok, 0ul, destructions.size()); 

      MTXASSERT_EQ(ok, 0, constructions["noot"]); 
      MTXASSERT_EQ(ok, 2ul, constructions.size()); 
      MTXASSERT_EQ(ok, 0ul, destructions.size()); 

      target_t hold; 
      { 
       target_t b = target_t(new Trackable("noot")), 
         c = target_t(new Trackable("mies")), 
         nil = Nil, 
         a2 = a; 

       MTXASSERT(ok, a2==a); 
       MTXASSERT(ok, nil!=a); 

       MTXASSERT_EQ(ok, 3ul, constructions.size()); 
       MTXASSERT_EQ(ok, 1, constructions["aap"]); 
       MTXASSERT_EQ(ok, 1, constructions["noot"]); 
       MTXASSERT_EQ(ok, 1, constructions["mies"]); 
       MTXASSERT_EQ(ok, 0, constructions["broer"]); 
       MTXASSERT_EQ(ok, 4ul, constructions.size()); 

       MTXASSERT_EQ(ok, 0ul, destructions.size()); 

       hold = b; 
      } 

      MTXASSERT_EQ(ok, 1ul, destructions.size()); 
      MTXASSERT_EQ(ok, 0, destructions["aap"]); 
      MTXASSERT_EQ(ok, 0, destructions["noot"]); 
      MTXASSERT_EQ(ok, 1, destructions["mies"]); 
      MTXASSERT_EQ(ok, 3ul, destructions.size()); 

      hold = Nil; 
      MTXASSERT_EQ(ok, 3ul, destructions.size()); 
      MTXASSERT_EQ(ok, 0, destructions["aap"]); 
      MTXASSERT_EQ(ok, 1, destructions["noot"]); 
      MTXASSERT_EQ(ok, 1, destructions["mies"]); 
      MTXASSERT_EQ(ok, 4ul, constructions.size()); 

      // ok, enuf for now 
      return ok; 
     } 

     struct Linked : Trackable 
     { 
      Linked(const std::string&t):Trackable(t){} 
      counted_ptr<Linked> next; 
     }; 

     bool testLinked() 
     { 
      bool ok = true; 

      constructions.clear(); 
      destructions.clear(); 
      MTXASSERT_EQ(ok, 0ul, constructions.size()); 
      MTXASSERT_EQ(ok, 0ul, destructions.size()); 

      counted_ptr<Linked> node(new Linked("parent")); 
      MTXASSERT(ok, node.get()); 
      node->next = counted_ptr<Linked>(new Linked("child")); 

      MTXASSERT_EQ(ok, 2ul, constructions.size()); 
      MTXASSERT_EQ(ok, 0ul, destructions.size()); 

      node = node->next; 
      MTXASSERT(ok, node.get()); 

      MTXASSERT_EQ(ok, 2ul, constructions.size()); 
      MTXASSERT_EQ(ok, 1ul, destructions.size()); 

      node = node->next; 
      MTXASSERT(ok,!node.get()); 

      MTXASSERT_EQ(ok, 2ul, constructions.size()); 
      MTXASSERT_EQ(ok, 2ul, destructions.size()); 

      return ok; 
     } 

    } 

} // EON 

int main() 
{ 
    using namespace MtxChess; 

    bool ok = true; 
    ok = testBehaviour() && ok; 
    ok = testLinked() && ok; 

    return ok?0:1; 
} 
+0

Gracias, pero también necesito la funcionalidad de weak_ptr – mpipe3

+0

¿Cómo se usan punteros inteligentes intrusivos con weak_ptrs? – mpipe3

0

Boost proporciona una macro se puede definir que no va a utilizar el conteo de referencia seguro para subprocesos.

+2

¿No es eso una cosa del tiempo de compilación? es decir, compilar la biblioteca de impulso con conteo de ref no seguro de hilos. Necesito un conteo seguro de subprocesos en otra parte de la misma base de código. – mpipe3

3

Andrei Alexandrescu habló de la implementación de su propia clase de puntero compartida de un solo subproceso (con algunas optimizaciones adicionales) en el CppCon 2014

ver el video here

Y las diapositivas here

Realmente piensan que la el estándar o boost deberían proporcionar un parámetro de plantilla para usar el conteo de ref atómico en sus ptrs compartidos, aunque ...

Cuestiones relacionadas