Esta es una pregunta de dos partes, todo sobre la atomicidad de std::shared_ptr
:
1.
Por lo que puedo decir, std::shared_ptr
es el único puntero inteligente <memory>
que es atómico. Me pregunto si hay una versión no atómica de std::shared_ptr
disponible (no puedo ver nada <memory>
, así que también estoy abierto a sugerencias fuera del estándar, como las de Boost). Sé boost::shared_ptr
que también es atómico (si BOOST_SP_DISABLE_THREADS
no está definido), pero ¿tal vez haya otra alternativa? Estoy buscando algo que tenga la misma semántica que std::shared_ptr
, pero sin la atomicidad.
2. Entiendo por qué std::shared_ptr
es atómico; es un poco agradable. Sin embargo, no es bueno para todas las situaciones, y C ++ ha tenido históricamente el mantra de "solo paga por lo que usas". Si no estoy usando varios subprocesos, o si estoy usando varios subprocesos pero no comparto la propiedad del puntero entre subprocesos, un puntero inteligente atómico es excesivo. Mi segunda pregunta es ¿por qué no se proporcionó una versión no atómica std::shared_ptr
en C ++ 11 ? (asumiendo que hay un por qué ) (si la respuesta es simplemente "una versión no atómica simplemente nunca se consideró" o "nadie pidió una versión no atómica", ¡está bien!).
Con la pregunta n. ° 2, me pregunto si alguien propuso alguna vez una versión no atómica de shared_ptr
(ya sea para Boost o el comité de estándares) (no para reemplazar la versión atómica shared_ptr
, sino para coexistir con ella) y fue rechazada por un razón específica.
fuente
shared_ptr
tuvo una desaceleración significativa debido a su atomicidad, y la definiciónBOOST_DISABLE_THREADS
hizo una diferencia notable (no sé sistd::shared_ptr
habría tenido el mismo costo que esoboost::shared_ptr
).shared_ptr
, no usa operaciones atómicas para el refcount. Consulte (2) en gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html para obtener un parche para GCC que permita que la implementación no atómica se use incluso en aplicaciones multiproceso, parashared_ptr
objetos que no se comparten entre hilos. He estado sentado en ese parche durante años, pero estoy considerando finalmente comprometerlo para GCC 4.9Respuestas:
No proporcionado por el estándar. Bien puede haber uno proporcionado por una biblioteca de "terceros". De hecho, antes de C ++ 11, y antes de Boost, parecía que todos escribían su propia referencia contada como puntero inteligente (incluyéndome a mí).
Esta cuestión se debatió en la reunión de Rapperswil en 2010. El tema fue presentado por Suiza en un Comentario del organismo nacional nº 20. Hubo argumentos sólidos en ambos lados del debate, incluidos los que proporcionó en su pregunta. Sin embargo, al final de la discusión, el voto fue abrumadoramente (pero no unánime) en contra de agregar una versión no sincronizada (no atómica) de
shared_ptr
.Los argumentos en contra incluyeron:
El código escrito con shared_ptr no sincronizado puede terminar usándose en código enhebrado en el futuro, causando problemas difíciles de depurar sin advertencia.
Tener un shared_ptr "universal" que sea "unidireccional" para realizar el tráfico en el recuento de referencias tiene ventajas: De la propuesta original :
El costo de las atómicas, aunque no es cero, no es abrumador. El costo se mitiga mediante el uso de la construcción de mudanzas y la asignación de mudanzas que no necesitan usar operaciones atómicas. Estas operaciones se utilizan comúnmente en
vector<shared_ptr<T>>
borrar e insertar.Nada prohíbe a las personas escribir su propio puntero inteligente contado por referencias no atómicas si eso es realmente lo que quieren hacer.
La última palabra del LWG en Rapperswil ese día fue:
fuente
Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries.
ese es un razonamiento extremadamente extraño. Las bibliotecas de terceros proporcionarán sus propios tipos de todos modos, entonces, ¿por qué importaría si lo proporcionaran bajo la forma de std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType>, etc.? siempre tendrá que adaptar su código a lo que devuelve la biblioteca de todos modosstd::shared_ptr<std::string>
. Si la biblioteca de otra persona también toma ese tipo, las personas que llaman pueden pasarnos las mismas cadenas de caracteres a ambos sin el inconveniente o la sobrecarga de convertir entre diferentes representaciones, y eso es una pequeña victoria para todos.Howard ya ha respondido bien a la pregunta, y Nicol hizo algunos buenos comentarios sobre los beneficios de tener un solo tipo de puntero compartido estándar, en lugar de muchos incompatibles.
Si bien estoy completamente de acuerdo con la decisión del comité, creo que hay algún beneficio en el uso de un
shared_ptr
tipo no sincronizado en casos especiales , por lo que he investigado el tema varias veces.Con GCC, cuando su programa no usa múltiples subprocesos, shared_ptr no usa operaciones atómicas para el refcount. Esto se hace actualizando los recuentos de referencias a través de funciones contenedoras que detectan si el programa es multiproceso (en GNU / Linux, esto se hace simplemente detectando si el programa se enlaza
libpthread.so
) y se envían a operaciones atómicas o no atómicas en consecuencia.Hace muchos años me di cuenta de que debido a que GCC
shared_ptr<T>
se implementa en términos de una__shared_ptr<T, _LockPolicy>
clase base , es posible usar la clase base con la política de bloqueo de un solo subproceso incluso en código multiproceso, usando explícitamente__shared_ptr<T, __gnu_cxx::_S_single>
. Desafortunadamente, debido a que ese no era un caso de uso previsto, no funcionó de manera óptima antes de GCC 4.9, y algunas operaciones aún usaban las funciones de envoltura y, por lo tanto, se enviaban a operaciones atómicas a pesar de que solicitó explícitamente la_S_single
política. Véase el punto (2) en http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmlpara obtener más detalles y un parche para GCC para permitir que la implementación no atómica se use incluso en aplicaciones multiproceso. Me senté en ese parche durante años, pero finalmente lo comprometí para GCC 4.9, que le permite usar una plantilla de alias como esta para definir un tipo de puntero compartido que no es seguro para subprocesos, pero es un poco más rápido:template<typename T> using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;
Este tipo no sería interoperable
std::shared_ptr<T>
y solo sería seguro de usar cuando se garantiza que losshared_ptr_unsynchronized
objetos nunca se compartirán entre subprocesos sin una sincronización adicional proporcionada por el usuario.Esto, por supuesto, es completamente no portátil, pero a veces está bien. Con los hacks de preprocesador adecuados, su código aún funcionaría bien con otras implementaciones si
shared_ptr_unsynchronized<T>
es un aliasshared_ptr<T>
, sería un poco más rápido con GCC.Si está usando un GCC antes de 4.9, podría usarlo agregando las
_Sp_counted_base<_S_single>
especializaciones explícitas a su propio código (y asegurándose de que nadie cree una instancia__shared_ptr<T, _S_single>
sin incluir las especializaciones, para evitar violaciones de ODR). Agregar tales especializaciones destd
tipos no está técnicamente definido, pero funciona en la práctica, porque en este caso no hay diferencia entre que yo agregue las especializaciones a GCC o usted las agregue a su propio código.fuente
std::shared_ptr
,std::__shared_ptr
,__default_lock_policy
y tal. Esta respuesta confirmó lo que entendí del código.Uno podría preguntarse con la misma facilidad por qué no hay un puntero intrusivo, o cualquier otra posible variación de punteros compartidos que uno podría tener.
El diseño de
shared_ptr
, transmitido desde Boost, ha sido crear una lengua franca estándar mínima de punteros inteligentes. Que, en términos generales, puedes quitarlo de la pared y usarlo. Es algo que se usaría de manera general, en una amplia variedad de aplicaciones. Puede ponerlo en una interfaz, y es probable que haya gente dispuesta a usarlo.Los subprocesos solo se volverán más frecuentes en el futuro. De hecho, a medida que pasa el tiempo, el enhebrado será generalmente uno de los medios principales para lograr el rendimiento. Requerir que el puntero inteligente básico haga lo mínimo necesario para admitir subprocesos facilita esta realidad.
Lanzar media docena de punteros inteligentes con variaciones menores entre ellos en el estándar, o peor aún, un puntero inteligente basado en políticas, habría sido terrible. Todos elegirían el puntero que más les guste y renunciarán a todos los demás. Nadie podría comunicarse con nadie más. Sería como las situaciones actuales con cadenas de C ++, donde cada uno tiene su propio tipo. Solo que es mucho peor, porque la interoperación con cadenas es mucho más fácil que la interoperación entre clases de punteros inteligentes.
Boost, y por extensión el comité, eligió un puntero inteligente específico para usar. Proporcionó un buen equilibrio de características y se usó ampliamente y comúnmente en la práctica.
std::vector
también tiene algunas ineficiencias en comparación con las matrices desnudas en algunos casos de esquina. Tiene algunas limitaciones; algunos usos realmente quieren tener un límite estricto en el tamaño de avector
, sin usar un asignador de lanzamiento. Sin embargo, el comité no se diseñóvector
para ser todo para todos. Fue diseñado para ser un buen valor predeterminado para la mayoría de las aplicaciones. Aquellos para quienes no puede funcionar pueden simplemente escribir una alternativa que se adapte a sus necesidades.Al igual que puede hacerlo con un puntero inteligente
shared_ptr
, la atomicidad es una carga. Por otra parte, también se podría considerar no copiarlos tanto.fuente
Estoy preparando una charla sobre shared_ptr en el trabajo. He estado usando un boost shared_ptr modificado con evitar malloc separado (como lo que puede hacer make_shared) y un parámetro de plantilla para la política de bloqueo como shared_ptr_unsynchronized mencionado anteriormente. Estoy usando el programa de
http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html
como prueba, después de limpiar las copias de shared_ptr innecesarias. El programa usa solo el hilo principal y se muestra el argumento de prueba. El entorno de prueba es un portátil que ejecuta linuxmint 14. Aquí está el tiempo empleado en segundos:
Solo la versión 'std' usa -std = cxx11, y el -pthread probablemente cambia lock_policy en la clase g ++ __shared_ptr.
A partir de estos números, veo el impacto de las instrucciones atómicas en la optimización del código. El caso de prueba no usa ningún contenedor C ++, pero
vector<shared_ptr<some_small_POD>>
es probable que sufra si el objeto no necesita la protección de subprocesos. Boost sufre menos probablemente porque el malloc adicional está limitando la cantidad de optimización de código y de inserción.Todavía tengo que encontrar una máquina con suficientes núcleos para probar la escalabilidad de las instrucciones atómicas, pero usar std :: shared_ptr solo cuando sea necesario es probablemente mejor.
fuente
Boost proporciona un
shared_ptr
que no es atómico. Se llamalocal_shared_ptr
y se puede encontrar en la biblioteca de punteros inteligentes de boost.fuente
shared_ptr
con mostrador de todos modos, aunque sea local? ¿O quiere decir que hay otro problema con eso? Los documentos dicen que la única diferencia es que esto no es atómico.local_shared_ptr
yshared_ptr
son idénticos excepto por atómico. Estoy realmente interesado en saber si lo que dice es cierto porque lo usolocal_shared_ptr
en aplicaciones que requieren un alto rendimiento.