Estaba viendo Manejo sistemático de errores en C ++: Andrei Alexandrescu afirma que las excepciones en C ++ son muy, muy lentas.
¿Sigue siendo cierto para C ++ 98?
Estaba viendo Manejo sistemático de errores en C ++: Andrei Alexandrescu afirma que las excepciones en C ++ son muy, muy lentas.
¿Sigue siendo cierto para C ++ 98?
Respuestas:
El modelo principal utilizado hoy para las excepciones (Itanium ABI, VC ++ 64 bits) es el modelo de excepciones de costo cero.
La idea es que en lugar de perder tiempo estableciendo una guardia y comprobando explícitamente la presencia de excepciones en todas partes, el compilador genera una tabla lateral que mapea cualquier punto que pueda lanzar una excepción (Contador de programa) a la lista de controladores. Cuando se lanza una excepción, se consulta esta lista para elegir el manejador correcto (si lo hay) y la pila se desenrolla.
Comparado con la
if (error)
estrategia típica :if
cuando ocurre una excepciónSin embargo, el costo no es trivial de medir:
dynamic_cast
prueba para cada controlador)Por lo tanto, la mayoría de los errores de caché no son triviales en comparación con el código de CPU puro.
Nota: para más detalles, lea el informe TR18015, capítulo 5.4 Manejo de excepciones (pdf)
Entonces, sí, las excepciones son lentas en el camino excepcional , pero por lo demás son más rápidas que las verificaciones explícitas (
if
estrategia) en general.Nota: Andrei Alexandrescu parece cuestionar esto "más rápido". Personalmente, he visto que las cosas cambian en ambos sentidos, algunos programas son más rápidos con excepciones y otros son más rápidos con ramas, por lo que parece haber una pérdida de optimizabilidad en ciertas condiciones.
Importa ?
Yo diría que no. Un programa debe escribirse teniendo en cuenta la legibilidad , no el rendimiento (al menos, no como un primer criterio). Se deben usar excepciones cuando se espera que la persona que llama no pueda o no quiera manejar la falla en el acto y pasarla a la pila. Bonificación: en C ++ 11, las excepciones se pueden clasificar entre subprocesos utilizando la biblioteca estándar.
Sin embargo, esto es sutil, afirmo que
map::find
no debería lanzar, pero estoy de acuerdo conmap::find
devolver unchecked_ptr
que arroja si un intento de desreferenciarlo falla porque es nulo: en el último caso, como en el caso de la clase que presentó Alexandrescu, la persona que llama elige entre la verificación explícita y la confianza en las excepciones. Empoderar a la persona que llama sin darle más responsabilidad suele ser una señal de buen diseño.fuente
abort
le permitirá medir la huella de tamaño binario y comprobar que el tiempo de carga / i-cache se comporta de manera similar. Por supuesto, mejor no pegarle a ninguno de losabort
...Cuando se publicó la pregunta, me dirigía al médico con un taxi esperando, por lo que solo tuve tiempo para un breve comentario. Pero habiendo comentado y votado a favor y en contra, será mejor que agregue mi propia respuesta. Incluso si la respuesta de Matthieu ya es bastante buena.
¿Las excepciones son especialmente lentas en C ++, en comparación con otros lenguajes?
Re el reclamo
Si eso es literalmente lo que dice Andrei, entonces por una vez es muy engañoso, si no totalmente equivocado. Para las excepciones generadas / lanzadas siempre es lento en comparación con otras operaciones básicas en el lenguaje, independientemente del lenguaje de programación . No solo en C ++ o más en C ++ que en otros lenguajes, como indica la supuesta afirmación.
En general, sobre todo independientemente del idioma, las dos características básicas del lenguaje que son órdenes de magnitud más lentas que el resto, porque se traducen en llamadas de rutinas que manejan estructuras de datos complejas, son
lanzamiento de excepciones, y
asignación de memoria dinámica.
Afortunadamente, en C ++ a menudo se pueden evitar ambos en el código de tiempo crítico.
Desafortunadamente, no existe tal cosa como un almuerzo gratis , incluso si la eficiencia predeterminada de C ++ se acerca bastante. :-) Por la eficiencia obtenida al evitar el lanzamiento de excepciones y la asignación dinámica de memoria, generalmente se logra codificando a un nivel más bajo de abstracción, usando C ++ simplemente como una "mejor C". Y una menor abstracción significa una mayor "complejidad".
Una mayor complejidad significa más tiempo dedicado al mantenimiento y poco o ningún beneficio de la reutilización del código, que son costos monetarios reales, incluso si son difíciles de estimar o medir. Es decir, con C ++ se puede, si se desea, cambiar algo de eficiencia del programador por eficiencia de ejecución. El hecho de hacerlo es en gran medida una decisión de ingeniería y intuitiva, porque en la práctica solo se puede estimar y medir fácilmente la ganancia, no el costo.
¿Existen medidas objetivas del rendimiento de lanzamiento de excepciones de C ++?
Sí, el comité internacional de estandarización de C ++ ha publicado un Informe técnico sobre el rendimiento de C ++, TR18015 .
¿Qué significa que las excepciones son “lentas”?
Básicamente, significa que a
throw
puede tomar un Very Long Time ™ en comparación con, por ejemplo, unaint
asignación, debido a la búsqueda de un manejador.Como TR18015 analiza en su sección 5.4 "Excepciones", hay dos estrategias principales de implementación de manejo de excepciones,
el enfoque en el que cada
try
bloque configura dinámicamente la captura de excepciones, de modo que se realiza una búsqueda en la cadena dinámica de controladores cuando se lanza una excepción, yel enfoque en el que el compilador genera tablas de búsqueda estáticas que se utilizan para determinar el controlador de una excepción lanzada.
El primer enfoque muy flexible y general es casi forzado en Windows de 32 bits, mientras que en la tierra de 64 bits y en * nix-land se usa comúnmente el segundo enfoque mucho más eficiente.
Además, como se analiza en ese informe, para cada enfoque hay tres áreas principales en las que el manejo de excepciones afecta la eficiencia:
try
-bloques,funciones regulares (oportunidades de optimización), y
throw
-expresiones.Básicamente, con el enfoque de controlador dinámico (Windows de 32 bits), el manejo de excepciones tiene un impacto en los
try
bloques, principalmente independientemente del idioma (porque esto se ve obligado por el esquema de manejo de excepciones estructurado de Windows ), mientras que el enfoque de tabla estática tiene un costo prácticamente nulo paratry
: bloques. Discutir esto tomaría mucho más espacio e investigación de lo que es práctico para una respuesta SO. Por lo tanto, consulte el informe para obtener más detalles.Desafortunadamente, el informe, de 2006, ya está un poco anticuado a fines de 2012 y, hasta donde yo sé, no hay nada comparable que sea más nuevo.
Otra perspectiva importante es que el impacto del uso de excepciones en el desempeño es muy diferente de la eficiencia aislada de las características del lenguaje de apoyo, porque, como señala el informe,
Por ejemplo:
Costos de mantenimiento debido a diferentes estilos de programación (corrección)
Verificación de
if
fallas en el sitio de llamadas redundantes versus centralizadatry
Problemas de almacenamiento en caché (por ejemplo, el código más corto puede caber en el caché)
El informe tiene una lista diferente de aspectos a considerar, pero de todos modos la única forma práctica de obtener hechos concretos sobre la eficiencia de la ejecución es probablemente implementar el mismo programa usando excepciones y no usando excepciones, dentro de un límite decidido en el tiempo de desarrollo y con desarrolladores. familiarizado con cada forma, y luego MEDIR .
¿Cuál es una buena forma de evitar la sobrecarga de excepciones?
La corrección casi siempre triunfa sobre la eficiencia.
Sin excepciones, lo siguiente puede suceder fácilmente:
Algunos códigos P están destinados a obtener un recurso o calcular alguna información.
El código de llamada C debería haber verificado el éxito / fracaso, pero no lo hace.
Un recurso inexistente o información inválida se usa en el código que sigue a C, causando un caos generalizado.
El principal problema es el punto (2), donde con el esquema de código de retorno habitual , el código de llamada C no está obligado a verificar.
Hay dos enfoques principales que fuerzan dicha verificación:
Donde P lanza directamente una excepción cuando falla.
Donde P devuelve un objeto que C tiene que inspeccionar antes de usar su valor principal (de lo contrario, una excepción o terminación).
El segundo enfoque fue AFAIK, descrito por primera vez por Barton y Nackman en su libro * Scientific and Engineering C ++: An Introduction with Advanced Techniques and Examples , donde introdujeron una clase llamada
Fallow
para un resultado de función "posible". Laoptional
biblioteca Boost ofrece ahora una clase similar llamada . Y puede implementar fácilmente unaOptional
clase usted mismo, utilizando unstd::vector
portador de valor para el caso de un resultado que no sea POD.Con el primer enfoque, el código de llamada C no tiene más remedio que utilizar técnicas de manejo de excepciones. Sin embargo, con el segundo enfoque, el código de llamada C puede decidir por sí mismo si realiza una
if
verificación basada o un manejo general de excepciones. Por lo tanto, el segundo enfoque permite hacer un intercambio entre el programador y la eficiencia del tiempo de ejecución.¿Cuál es el impacto de los distintos estándares de C ++ en el rendimiento de las excepciones?
C ++ 98 fue el primer estándar de C ++. Para las excepciones, introdujo una jerarquía estándar de clases de excepción (lamentablemente bastante imperfecta). El principal impacto en el rendimiento fue la posibilidad de especificaciones de excepción (eliminadas en C ++ 11), que sin embargo nunca fueron completamente implementadas por el compilador principal de Windows C ++ Visual C ++: Visual C ++ acepta la sintaxis de especificación de excepción de C ++ 98, pero simplemente ignora especificaciones de excepción.
C ++ 03 era solo una corrección técnica de C ++ 98. Lo único realmente nuevo en C ++ 03 fue la inicialización de valor . Lo que no tiene nada que ver con las excepciones.
Con el estándar C ++ 11, se eliminaron las especificaciones de excepción general y se reemplazaron con la
noexcept
palabra clave.El estándar C ++ 11 también agregó soporte para almacenar y volver a generar excepciones, lo cual es excelente para propagar excepciones de C ++ a través de devoluciones de llamada en lenguaje C. Este soporte restringe efectivamente cómo se puede almacenar la excepción actual. Sin embargo, hasta donde yo sé, eso no afecta el rendimiento, excepto en la medida en que, en el código más nuevo, el manejo de excepciones puede usarse más fácilmente en ambos lados de una devolución de llamada en lenguaje C.
fuente
longjmp
debe usar el controlador.try..finally
construcción se puede implementar sin desenrollar la pila. F #, C # y Java se implementantry..finally
sin usar el desenrollado de pila. Sololongjmp
al manejador (como ya expliqué).Nunca puede reclamar sobre el rendimiento a menos que convierta el código al ensamblado o lo compare.
Esto es lo que ve: (banco rápido)
El código de error no es sensible al porcentaje de ocurrencia. Las excepciones tienen un poco de sobrecarga siempre y cuando nunca se descarten. Una vez que los arrojas, comienza la miseria. En este ejemplo, se arroja para 0%, 1%, 10%, 50% y 90% de los casos. Cuando las excepciones se lanzan el 90% del tiempo, el código es 8 veces más lento que en el caso en el que las excepciones se lanzan el 10% del tiempo. Como ve, las excepciones son realmente lentas. No los use si se arrojan con frecuencia. Si su aplicación no tiene requisitos de tiempo real, no dude en lanzarlos si ocurren muy raramente.
Ves muchas opiniones contradictorias sobre ellos. Pero finalmente, ¿las excepciones son lentas? Yo no juzgo. Solo mira el punto de referencia.
fuente
Depende del compilador.
GCC, por ejemplo, era conocido por tener un rendimiento muy bajo en el manejo de excepciones, pero esto mejoró considerablemente en los últimos años.
Pero tenga en cuenta que el manejo de excepciones debe, como su nombre lo indica, ser la excepción y no la regla en el diseño de su software. Cuando tiene una aplicación que arroja tantas excepciones por segundo que afecta el rendimiento y esto todavía se considera un funcionamiento normal, entonces debería pensar en hacer las cosas de manera diferente.
Las excepciones son una excelente manera de hacer que el código sea más legible al eliminar todo ese código torpe de manejo de errores, pero tan pronto como se convierten en parte del flujo normal del programa, se vuelven realmente difíciles de seguir. Recuerde que a
throw
es prácticamente ungoto catch
disfraz.fuente
throw new Exception
es un Java-ismo. Por regla general, nunca se deben lanzar punteros.Sí, pero eso no importa. ¿Por qué?
Lea esto:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
Básicamente, eso dice que usar excepciones como las que describió Alexandrescu (desaceleración 50x porque usan
catch
comoelse
) es simplemente incorrecto. Dicho esto, para las personas a las que les gusta hacerlo así, desearía que C ++ 22 :) agregara algo como:(tenga en cuenta que esto debería ser el lenguaje central, ya que básicamente es un compilador que genera código a partir de uno existente)
PD también tenga en cuenta que incluso si las excepciones son tan lentas ... no es un problema si no pasa mucho tiempo en esa parte del código durante la ejecución ... Por ejemplo, si la división flotante es lenta y la hace 4x más rápido, eso no importa si pasa el 0.3% de su tiempo haciendo la división FP ...
fuente
Como dijo In Silico, su implementación depende, pero en general las excepciones se consideran lentas para cualquier implementación y no deben usarse en código de alto rendimiento.
EDITAR: No estoy diciendo que no los use en absoluto, pero para el código de rendimiento intensivo es mejor evitarlos.
fuente