¿Cuánto es la sobrecarga de los punteros inteligentes en comparación con los punteros normales en C ++ 11? En otras palabras, ¿mi código será más lento si utilizo punteros inteligentes y, de ser así, cuánto más lento?
Específicamente, estoy preguntando sobre C ++ 11 std::shared_ptr
y std::unique_ptr
.
Obviamente, las cosas empujadas hacia abajo en la pila serán más grandes (al menos eso creo), porque un puntero inteligente también necesita almacenar su estado interno (recuento de referencias, etc.), la pregunta realmente es, ¿cuánto va a ser esto? afectar mi rendimiento, si es que lo hace?
Por ejemplo, devuelvo un puntero inteligente de una función en lugar de un puntero normal:
std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();
O, por ejemplo, cuando una de mis funciones acepta un puntero inteligente como parámetro en lugar de un puntero normal:
void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);
fuente
std::unique_ptr
ostd::shared_ptr
?Respuestas:
std::unique_ptr
tiene sobrecarga de memoria solo si le proporciona algún eliminador no trivial.std::shared_ptr
siempre tiene sobrecarga de memoria para el contador de referencia, aunque es muy pequeña.std::unique_ptr
tiene sobrecarga de tiempo solo durante el constructor (si tiene que copiar el eliminador proporcionado y / o inicializar el puntero en nulo) y durante el destructor (para destruir el objeto de propiedad).std::shared_ptr
tiene sobrecarga de tiempo en el constructor (para crear el contador de referencia), en el destructor (para disminuir el contador de referencia y posiblemente destruir el objeto) y en el operador de asignación (para incrementar el contador de referencia). Debido a las garantías de seguridad de subprocesos destd::shared_ptr
, estos incrementos / decrementos son atómicos, lo que agrega algo más de sobrecarga.Tenga en cuenta que ninguno de ellos tiene una sobrecarga de tiempo en la eliminación de referencias (para obtener la referencia al objeto de propiedad), mientras que esta operación parece ser la más común para los punteros.
En resumen, hay algunos gastos generales, pero no debería ralentizar el código a menos que cree y destruya continuamente punteros inteligentes.
fuente
unique_ptr
no tiene gastos generales en el destructor. Hace exactamente lo mismo que lo haría con un puntero sin formato.std::unique_ptr
? Si construye astd::unique_ptr<int>
, el internoint*
se inicializa,nullptr
le guste o no.Como ocurre con todo el rendimiento del código, el único medio realmente confiable para obtener información sólida es medir y / o inspeccionar el código de la máquina.
Dicho esto, el razonamiento simple dice que
Puede esperar algo de sobrecarga en las compilaciones de depuración, ya que, por ejemplo,
operator->
debe ejecutarse como una llamada de función para que pueda ingresar (esto a su vez se debe a la falta general de soporte para marcar clases y funciones como no depuradas).Porque
shared_ptr
puede esperar cierta sobrecarga en la creación inicial, ya que eso implica la asignación dinámica de un bloque de control, y la asignación dinámica es mucho más lenta que cualquier otra operación básica en C ++ (utilícelamake_shared
cuando sea posible para minimizar esa sobrecarga).También porque
shared_ptr
existe una sobrecarga mínima para mantener un recuento de referencia, por ejemplo, cuando se pasa unshared_ptr
valor por, pero no hay tal sobrecarga paraunique_ptr
.Teniendo en cuenta el primer punto anterior, cuando mida, hágalo tanto para depurar como para versiones de lanzamiento.
El comité internacional de estandarización de C ++ ha publicado un informe técnico sobre el rendimiento , pero esto fue en 2006, antes
unique_ptr
yshared_ptr
se agregó a la biblioteca estándar. Aún así, los indicadores inteligentes eran viejos en ese momento, por lo que el informe también consideró eso. Citando la parte relevante:Como conjetura informada, el "bien dentro del estado de la técnica" se ha logrado con los compiladores más populares en la actualidad, a principios de 2014.
fuente
Mi respuesta es diferente a las demás y realmente me pregunto si alguna vez perfilaron el código.
shared_ptr tiene una sobrecarga significativa para la creación debido a su asignación de memoria para el bloque de control (que mantiene el contador de referencias y una lista de punteros a todas las referencias débiles). También tiene una sobrecarga de memoria enorme debido a esto y al hecho de que std :: shared_ptr es siempre una tupla de 2 punteros (uno al objeto, otro al bloque de control).
Si pasa un puntero_compartido a una función como un parámetro de valor, entonces será al menos 10 veces más lento que una llamada normal y creará muchos códigos en el segmento de código para el desenrollado de la pila. Si lo pasa por referencia, obtiene una indirección adicional que también puede ser bastante peor en términos de rendimiento.
Es por eso que no debe hacer esto a menos que la función esté realmente involucrada en la gestión de la propiedad. De lo contrario, utilice "shared_ptr.get ()". No está diseñado para asegurarse de que su objeto no se elimine durante una llamada de función normal.
Si te vuelves loco y usas shared_ptr en objetos pequeños como un árbol de sintaxis abstracta en un compilador o en nodos pequeños en cualquier otra estructura gráfica, verás una gran caída de rendimiento y un gran aumento de memoria. He visto un sistema analizador que se reescribió poco después de que C ++ 14 llegara al mercado y antes de que el programador aprendiera a usar correctamente los punteros inteligentes. La reescritura fue una magnitud más lenta que el código anterior.
No es una solución milagrosa y los consejos crudos tampoco son malos por definición. Los malos programadores son malos y el mal diseño es malo. Diseñe con cuidado, diseñe con una propiedad clara en mente e intente usar shared_ptr principalmente en el límite de la API del subsistema.
Si desea obtener más información, puede ver la buena charla de Nicolai M. Josuttis sobre "El precio real de los punteros compartidos en C ++" https://vimeo.com/131189627
Profundiza en los detalles de implementación y la arquitectura de la CPU para las barreras de escritura, atómicas bloqueos, etc. una vez que escuche, nunca hablará de que esta función sea barata. Si solo desea una prueba de la magnitud más lenta, omita los primeros 48 minutos y observe cómo ejecuta un código de ejemplo que se ejecuta hasta 180 veces más lento (compilado con -O3) cuando usa un puntero compartido en todas partes.
fuente
std::make_shared()
? Además, encuentro que las demostraciones de mal uso descarado son un poco aburridas ...En otras palabras, ¿mi código será más lento si utilizo punteros inteligentes y, de ser así, cuánto más lento?
¿Más lento? Lo más probable es que no, a menos que esté creando un índice enorme usando shared_ptrs y no tenga suficiente memoria hasta el punto en que su computadora comience a arrugarse, como una anciana que cae al suelo por una fuerza insoportable desde lejos.
Lo que haría que su código fuera más lento son las búsquedas lentas, el procesamiento de bucle innecesario, grandes copias de datos y muchas operaciones de escritura en el disco (como cientos).
Las ventajas de un puntero inteligente están todas relacionadas con la gestión. ¿Pero es necesaria la sobrecarga? Esto depende de su implementación. Digamos que está iterando sobre una matriz de 3 fases, cada fase tiene una matriz de 1024 elementos. Crear un
¿Pero realmente quieres hacer eso?smart_ptr
para este proceso puede ser exagerado, ya que una vez que finalice la iteración, sabrá que debe borrarlo. Por lo tanto, podría obtener memoria adicional al no usar unsmart_ptr
...Una sola fuga de memoria podría hacer que su producto tenga un punto de falla en el tiempo (digamos que su programa pierde 4 megabytes cada hora, tomaría meses romper una computadora, sin embargo, se romperá, lo sabe porque la fuga está ahí) .
Es como decir "tu software está garantizado por 3 meses, entonces llámame para servicio".
Entonces, al final, realmente es una cuestión de ... ¿puedes manejar este riesgo? ¿Vale la pena perder el control de la memoria usando un puntero sin procesar para manejar su indexación sobre cientos de objetos diferentes?
Si la respuesta es sí, utilice un puntero sin formato.
Si ni siquiera quiere considerarlo, a
smart_ptr
es una solución buena, viable e increíble.fuente
smart_ptr
son realmente útiles para equipos grandesThats why you should not do this unless the function is really involved in ownership management
... gran respuesta, gracias, upvotedSolo para echar un vistazo y solo para el
[]
operador, es ~ 5 veces más lento que el puntero en bruto como se demuestra en el siguiente código, que se compiló utilizandogcc -lstdc++ -std=c++14 -O0
y generó este resultado:Estoy empezando a aprender c ++, tengo esto en mente: siempre necesitas saber qué estás haciendo y tomarte más tiempo para saber lo que otros habían hecho en tu c ++.
EDITAR
Como mencionó @Mohan Kumar, proporcioné más detalles. La versión gcc es
7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1)
, El resultado anterior se obtuvo cuando-O0
se usa, sin embargo, cuando uso la bandera '-O2', obtengo esto:Luego cambió a
clang version 3.9.0
,-O0
fue:-O2
estaba:El resultado del clang
-O2
es asombroso.fuente
-O0
ni depure código. La salida será extremadamente ineficiente . Siempre use al menos-O2
(o-O3
hoy en día porque no se realiza alguna vectorización-O2
)free
llamada en la prueba malloc, ydelete[]
para new (o hacer variablea
estática), porque losunique_ptr
s están llamandodelete[]
bajo el capó, en sus destructores.