programacion - ¿Cuánto es la sobrecarga de los punteros inteligentes en comparación con los punteros normales en C++?
punteros inteligentes c++ (4)
¿Cuánto cuesta la sobrecarga de los punteros inteligentes en comparación con los punteros normales en C ++ 11? En otras palabras, ¿mi código va a ser más lento si uso punteros inteligentes, y si es 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 van a ser 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 hacer 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);
Como con todo el rendimiento del código, el único medio realmente confiable para obtener información dura es medir y / o inspeccionar el código de la máquina.
Dicho eso, el razonamiento simple dice que
Puede esperar cierta sobrecarga en compilaciones de depuración, ya que, por ejemplo,
operator->
debe ejecutarse como una llamada a función para que pueda acceder a ella (esto a su vez se debe a la falta general de soporte para marcar clases y funciones como no depuradas).Para
shared_ptr
puede esperar cierta sobrecarga en la creación inicial, ya que 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 ++ (utilicemake_shared
cuando sea posible, para minimizar esa sobrecarga).También para
shared_ptr
hay una sobrecarga mínima en el mantenimiento de un recuento de referencias, por ejemplo, al pasar unshared_ptr
por valor, pero no hay tal sobrecarga paraunique_ptr
.
Teniendo en cuenta el primer punto anterior, cuando lo midas, hazlo tanto para depurar como para lanzar compilaciones.
El comité internacional de normalización de C ++ ha publicado un informe técnico sobre el rendimiento , pero fue en 2006, antes de que se unique_ptr
y shared_ptr
a la biblioteca estándar. Aún así, los indicadores inteligentes eran viejos en ese punto, por lo que el informe también consideró eso. Citando la parte relevante:
"Si acceder a un valor a través de un puntero inteligente trivial es significativamente más lento que acceder a él a través de un puntero común, el compilador maneja ineficientemente la abstracción. En el pasado, la mayoría de los compiladores tenían importantes penalizaciones por abstracción y muchos compiladores actuales aún lo hacen. Sin embargo, se ha informado que al menos dos compiladores tienen sanciones de abstracción por debajo del 1% y otra una penalización del 3%, por lo que eliminar este tipo de sobrecarga está dentro del estado de la técnica ".
Como una suposición informada, el "bien dentro del estado del arte" se ha logrado con los compiladores más populares hoy en día, a partir de principios de 2014.
Mi respuesta es diferente a las demás y realmente me pregunto si alguna vez crearon un perfil de 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 gran sobrecarga de memoria debido a esto y al hecho de que std :: shared_ptr siempre es una tupla de 2 punteros (una para el objeto, una para el bloque de control).
Si pasa un puntero compartido a una función como parámetro de valor, 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 un indirecto adicional que también puede ser bastante peor en términos de rendimiento.
Es por eso que no debes hacer esto a menos que la función esté realmente involucrada en la administración de la propiedad. De lo contrario, use "shared_ptr.get ()". No está diseñado para asegurarse de que su objeto no muera 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 de gráfico, 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 punteros inteligentes correctamente. La reescritura fue una magnitud más lenta que el código anterior.
No es una bala de plata y los indicadores crudos tampoco son malos por definición. Los malos programadores son malos y el diseño malo es malo. Diseñe con cuidado, diseñe con una clara propiedad en mente y trate de usar el shared_ptr principalmente en el límite de la API del subsistema.
Si desea obtener más información, puede ver a Nicolai M. Josuttis hablar sobre "El precio real de los indicadores compartidos en C ++" https://vimeo.com/131189627
Se profundiza en los detalles de implementación y en la arquitectura de la CPU para barreras de escritura, bloqueos atómicos, etc. Una vez que escuchas, nunca hablarás de que esta característica es 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 se utiliza puntero compartido en todas partes.
std::unique_ptr
tiene una sobrecarga de memoria solo si le proporciona algún tipo de eliminador no trivial.
std::shared_ptr
siempre tiene una sobrecarga de memoria para el contador de referencia, aunque es muy pequeña.
std::unique_ptr
tiene una sobrecarga de tiempo solo durante el constructor (si tiene que copiar el eliminador proporcionado y / o nulo-inicializar el puntero) y durante el destructor (para destruir el objeto propiedad).
std::shared_ptr
tiene una 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 de std::shared_ptr
, estos incrementos / decrementos son atómicos, lo que agrega un poco más de sobrecarga.
Tenga en cuenta que ninguno de ellos tiene una sobrecarga de tiempo en la desreferenciación (al obtener la referencia al objeto propiedad), mientras que esta operación parece ser la más común para los punteros.
En resumen, hay una sobrecarga, pero no debería hacer que el código sea lento a menos que crees y destruyas continuamente los punteros inteligentes.
En otras palabras, ¿mi código va a ser más lento si uso punteros inteligentes, y si es 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 caída al suelo por una fuerza insoportable desde lejos.
Lo que haría que tu código fuera más lento son las búsquedas lentas, el procesamiento de bucle innecesario, las enormes 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 necesario el gasto? Esto depende de tu implementación. Digamos que estás iterando sobre una matriz de 3 fases, cada fase tiene una matriz de 1024 elementos. Crear un smart_ptr
para este proceso puede ser excesivo, ya que una vez que smart_ptr
la iteración sabrá que debe borrarlo. Para que pueda ganar memoria extra al no usar un smart_ptr
...
Una sola pérdida de memoria puede hacer que su producto tenga un punto de falla en el tiempo (digamos que su programa pierde 4 megabytes cada hora, le 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 por servicio".
Entonces, al final, realmente es una cuestión de ... ¿puedes manejar este riesgo? Si usa un puntero sin formato para manejar su indexación sobre cientos de objetos diferentes, vale la pena perder el control de la memoria.
Si la respuesta es sí, entonces use un puntero sin formato.
Si ni siquiera quiere considerarlo, un smart_ptr
es una solución buena, viable e impresionante.