Pregunta ¿Hay un equivalente no atómico de std :: shared_ptr? ¿Y por qué no hay uno en ?


Esta es una pregunta de dos partes, todo sobre la atomicidad de std::shared_ptr:

1. Por lo que yo puedo decir, std::shared_ptr es el único puntero inteligente en <memory> eso es atómico Me pregunto si hay una versión no atómica de std::shared_ptr disponible (no puedo ver nada en <memory>, así que también estoy abierto a sugerencias fuera del estándar, como las de Boost). Lo sé boost::shared_ptr también es atómico (si BOOST_SP_DISABLE_THREADS no está definido), pero tal vez hay otra alternativa? Estoy buscando algo que tenga la misma semántica que std::shared_ptr, pero sin la atomicidad.

2. entiendo porque std::shared_ptr es atómico; es un poco agradable. Sin embargo, no es bueno para todas las situaciones, y C ++ históricamente ha tenido el mantra de "solo pagar por lo que usa". Si no estoy usando varios subprocesos, o si estoy usando varios subprocesos pero no comparto la propiedad del puntero entre los subprocesos, un puntero inteligente atómico es excesivo. Mi segunda pregunta es ¿por qué no era una versión no atómica de std::shared_ptr provisto en C ++ 11? (suponiendo que haya 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 # 2, me pregunto si alguien alguna vez propuso una versión no atómica de shared_ptr (ya sea a Boost o al comité de estándares) (no para reemplazar la versión atómica de shared_ptr, pero para convivir con él) y fue derribado por una razón específica.


74
2018-02-28 06:49


origen


Respuestas:


1. Me pregunto si hay una versión no atómica de std :: shared_ptr disponible

No proporcionado por el estándar. Es posible que haya una proporcionada por una biblioteca de "terceros". De hecho, antes de C ++ 11, y antes de Boost, parecía que todos escribieron su propio puntero inteligente de referencia contado (incluyéndome a mí).

2. Mi segunda pregunta es ¿por qué no fue una versión no atómica de std :: shared_ptr proporcionada en C ++ 11?

Esta cuestión se discutió en la reunión de Rapperswil en 2010. El tema fue presentado por un comentario del organismo nacional # 20 por Suiza. Hubo fuertes argumentos en ambos lados del debate, incluidos los que usted proporciona 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.

Argumentos en contra:

  • El código escrito con el shared_ptr no sincronizado puede terminar siendo utilizado en el código de subprocesos más adelante, lo que termina causando problemas difíciles de depurar sin previo aviso.

  • Tener un "shared_ptr" universal que es la "única forma" de tráfico en el recuento de referencias tiene beneficios: desde la propuesta original:

    Tiene el mismo tipo de objeto independientemente de las características utilizadas, lo que facilita en gran medida la interoperabilidad entre bibliotecas, incluidas las bibliotecas de terceros.

  • El costo de los atómicos, aunque no es cero, no es abrumador. El costo se mitiga mediante el uso de la construcción de movimiento y la asignación de movimiento que no necesitan utilizar operaciones atómicas. Tales operaciones se usan comúnmente en vector<shared_ptr<T>>borrar e insertar.

  • Nada prohíbe a las personas escribir su propio puntero inteligente no contabilizado de referencia atómica si eso es realmente lo que quieren hacer.

La última palabra del LWG en Rapperswil ese día fue:

Rechazar CH 20. No hay consenso para hacer un cambio en este momento.


84
2018-02-28 16:10



Howard ya respondió bien 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.

Aunque estoy completamente de acuerdo con la decisión del comité, creo que hay algún beneficio al usar un sistema no sincronizado shared_ptrtipo de tipo en casos especiales, así que he investigado el tema algunas veces.

Si no estoy usando varios subprocesos, o si estoy usando varios subprocesos pero no comparto la propiedad del puntero entre los subprocesos, un puntero inteligente atómico es excesivo.

Con GCC cuando su programa no usa múltiples hilos shared_ptr no usa operaciones atómicas para el recuento. Esto se hace actualizando los recuentos de referencia a través de funciones de envoltura que detectan si el programa tiene múltiples subprocesos (en GNU / Linux esto se hace simplemente al detectar si el programa se vincula a libpthread.so) y el envío a operaciones atómicas o no atómicas en consecuencia.

Me di cuenta hace muchos años que porque 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, mediante el uso explícito __shared_ptr<T, __gnu_cxx::_S_single>. Desafortunadamente, debido a que no era un caso de uso previsto, no funcionó de manera óptima antes de GCC 4.9, y algunas operaciones aún utilizaban las funciones de envoltura y, por lo tanto, se enviaban a operaciones atómicas a pesar de que solicitó explícitamente el _S_single política. Ver el punto (2) en http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html para obtener más detalles y un parche para GCC para permitir que la implementación no atómica se use incluso en aplicaciones multiproceso. Estuve sentado 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 con std::shared_ptr<T> y solo sería seguro de usar cuando se garantice que el shared_ptr_unsynchronized los objetos nunca se compartirían entre hilos sin una sincronización adicional proporcionada por el usuario.

Esto es, por supuesto, completamente no portátil, pero a veces eso está bien. Con el preprocesador correcto piratea su código aún funcionaría bien con otras implementaciones si shared_ptr_unsynchronized<T> es un alias para shared_ptr<T>, sería un poco más rápido con GCC.

Si está utilizando un GCC antes de 4.9, puede usarlo agregando el _Sp_counted_base<_S_single> especializaciones explícitas a tu propio código (y asegurando que nadie alguna vez instancia) __shared_ptr<T, _S_single> sin incluir las especializaciones, para evitar violaciones de ODR). Agregar tales especializaciones de std tipos es técnicamente indefinido, pero funcionaría en la práctica, porque en este caso no hay diferencia entre agregar las especializaciones a GCC o agregarlas a su propio código.


41
2018-02-28 17:26



Mi segunda pregunta es por qué no fue una versión atómica de std :: shared_ptr proporcionada en C ++ 11? (suponiendo que hay un por qué)

Uno podría fácilmente preguntar por qué no hay un puntero intrusivo o cualquier cantidad de otras posibles variaciones de punteros compartidos que uno pueda tener.

El diseño de shared_ptr, heredado de Boost, ha sido crear una lingua-franca estándar mínima de punteros inteligentes. Eso, en términos generales, puedes tirar de esto hacia abajo y usarlo. Es algo que se usaría generalmente en una amplia variedad de aplicaciones. Puedes ponerlo en una interfaz, y hay buenas probabilidades de que la gente esté dispuesta a usarlo.

Enhebrar solo va a obtener Más prevalente en el futuro De hecho, a medida que pasa el tiempo, el enhebrado generalmente será uno de los principales medios para lograr el rendimiento. Requerir que el puntero inteligente básico haga el mínimo necesario para admitir el enhebrado facilita esta realidad.

Dejar caer media docena de punteros inteligentes con variaciones menores entre ellos en el estándar, o incluso peor un puntero inteligente basado en políticas, habría sido terrible. Todos elegirían el puntero que más les gusta 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 fue ampliamente utilizado en la práctica.

std::vector tiene algunas ineficiencias en comparación con las matrices desnudas en algunos casos de esquina también. Tiene algunas limitaciones; algunos usos realmente quieren tener un límite estricto en el tamaño de un vector, sin usar un asignador de lanzamiento. Sin embargo, el comité no diseñó vector 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 trabajar pueden simplemente escribir una alternativa que satisfaga sus necesidades.

Del mismo modo que puede hacerlo con un puntero inteligente si la atomicidad de shared_ptr es una carga. Por otra parte, uno también podría considerar no copiarlos tanto.


20
2018-02-28 07:14



Estoy preparando una charla sobre shared_ptr en el trabajo. He estado utilizando un impulso compartido shared_ptr modificado para evitar malloc por 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 shared_ptr innecesarias. El programa usa solo el hilo principal y se muestra el argumento de prueba. El env de prueba es un portátil que ejecuta linuxmint 14. Aquí está el tiempo tomado en segundos:

configuración de ejecución de prueba boost (1.49) std con make_shared boost modificado
mt-inseguro (11) 11.9 9 / 11.5 (-pthread on) 8.4
atómico (11) 13.6 12.4 13.0
mt-inseguro (12) 113.5 85.8 / 108.9 (-pthread on) 81.5
atómico (12) 126.0 109.1 123.6

Solo la versión 'estándar' usa -std = cxx11, y la -pthread probablemente cambie a 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 contenedores C ++, pero vector<shared_ptr<some_small_POD>> es probable que sufra si el objeto no necesita la protección de hilo. Boost sufre menos probablemente debido a que el malloc adicional está limitando la cantidad de creación y la optimización del código.

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.


4
2018-05-30 04:44