¿Son las excepciones en C ++ realmente lentas?

99

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?

Avinash
fuente
43
No tiene sentido preguntar si las "excepciones de C ++ 98" son más rápidas / más lentas que las "excepciones de C ++ 03" o las "excepciones de C ++ 11". Su rendimiento depende de cómo el compilador los implemente en sus programas, y el estándar C ++ no dice nada sobre cómo deben implementarse; el único requisito es que su comportamiento debe seguir el estándar (la regla "como si").
In silico
Pregunta relacionada (pero no realmente duplicada): stackoverflow.com/questions/691168/…
Philipp
2
sí, es muy lento, pero no deben arrojarse para operaciones normales o usarse como una rama
BЈовић
Encontré una pregunta similar .
PaperBirdMaster
Para aclarar lo que ha dicho BЈовић, el uso de excepciones no es algo que deba temer. Cuando se lanza una excepción, se encuentra con operaciones (potencialmente) que consumen mucho tiempo. También tengo curiosidad por saber por qué desea saber para C ++ 89 específicamente ... que la última versión es C ++ 11, y el tiempo que tardan en ejecutarse las excepciones está definido por la implementación, de ahí mi 'potencialmente' consumo de tiempo .
thecoshman

Respuestas:

162

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 :

  • el modelo de costo cero, como su nombre lo indica, es gratuito cuando no ocurren excepciones
  • Cuesta alrededor de 10x / 20x ifcuando ocurre una excepción

Sin embargo, el costo no es trivial de medir:

  • La mesa auxiliar generalmente está fría y, por lo tanto, recuperarla de la memoria lleva mucho tiempo.
  • La determinación del controlador correcto implica RTTI: muchos descriptores RTTI para recuperar, dispersos por la memoria y operaciones complejas para ejecutar (básicamente una dynamic_castprueba 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 ( ifestrategia) 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::findno debería lanzar, pero estoy de acuerdo con map::finddevolver un checked_ptrque 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.

Matthieu M.
fuente
3
+1 Solo agregaría cuatro cosas: (0) sobre el soporte para relanzamiento agregado en C ++ 11; (1) una referencia al informe del comité sobre la eficiencia de c ++; (2) algunas observaciones acerca de la corrección (que superan incluso la legibilidad); y (3) sobre el desempeño, comentarios sobre cómo compararlo con el caso de no usar excepciones (todo es relativo)
Cheers y hth. - Alf
2
@ Cheersandhth.-Alf: (0), (1) y (3) hecho: gracias. Con respecto a la corrección (2), si bien supera la legibilidad, no estoy seguro de que las excepciones conduzcan a un código más correcto que otras estrategias de manejo de errores (es tan fácil olvidarse de las muchas rutas invisibles de ejecución que crean las excepciones).
Matthieu M.
2
La descripción puede ser localmente correcta, pero vale la pena señalar que la presencia de excepciones tiene implicaciones globales sobre las suposiciones y optimizaciones que puede realizar el compilador. Estas implicaciones sufren el problema de que no tienen "contraejemplos triviales", ya que el compilador siempre puede ver a través de un programa pequeño. Crear perfiles sobre una base de código grande y realista con y sin excepciones puede ser una buena idea.
Kerrek SB
4
> el modelo de coste cero, como su nombre lo indica, es gratuito cuando no se produce ninguna excepción, esto no es cierto hasta el más mínimo nivel de detalle. generar más código siempre tiene un impacto en el rendimiento, incluso si es pequeño y sutil ... el sistema operativo puede tardar un poco más en cargar el ejecutable, o obtendrá más errores de i-cache. también, ¿qué pasa con el código de desenrollado de pila? Además, ¿qué pasa con los experimentos que puedes hacer para medir los efectos en lugar de tratar de comprenderlos con el pensamiento racional?
jheriko
2
@jheriko: Creo que ya he abordado la mayoría de sus preguntas. El tiempo de carga no debe verse afectado (no se debe cargar el código en frío), el i-cache no debe verse afectado (el código en frío no debe entrar en el i-cache), ... así que para abordar la única pregunta que falta: "cómo medir" => reemplazar cualquier excepción lanzada con una llamada a abortle 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 los abort...
Matthieu M.
60

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

"Estaba viendo Manejo de errores sistemáticos en C ++. Andrei Alexandrescu afirma que las excepciones en C ++ son muy, muy lentas".

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 throwpuede tomar un Very Long Time ™ en comparación con, por ejemplo, una intasignació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 trybloque 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, y

  • el 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 trybloques, 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 para try: 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,

"Al considerar el manejo de excepciones, debe contrastarse con formas alternativas de lidiar con los errores".

Por ejemplo:

  • Costos de mantenimiento debido a diferentes estilos de programación (corrección)

  • Verificación de iffallas 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:

  1. Algunos códigos P están destinados a obtener un recurso o calcular alguna información.

  2. El código de llamada C debería haber verificado el éxito / fracaso, pero no lo hace.

  3. 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 Fallowpara un resultado de función "posible". La optionalbiblioteca Boost ofrece ahora una clase similar llamada . Y puede implementar fácilmente una Optionalclase usted mismo, utilizando un std::vectorportador 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 ifverificació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?

"Quiero saber si esto sigue siendo cierto para C ++ 98"

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 noexceptpalabra 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.

Saludos y hth. - Alf
fuente
6
"las excepciones siempre es lento en comparación con otras operaciones básicas en el lenguaje, independientemente del lenguaje de programación" ... excepto en lenguajes diseñados para compilar el uso de excepciones en el control de flujo ordinario.
Ben Voigt
4
"Lanzar una excepción implica tanto la asignación como el desenrollado de la pila". Obviamente, eso tampoco es cierto en general y, nuevamente, OCaml es un contraejemplo. En los lenguajes de recolección de basura, no es necesario desenrollar la pila porque no hay destructores, por lo que solo longjmpdebe usar el controlador.
JD
2
@JonHarrop: presumiblemente no sabe que Pyhon tiene una cláusula final para el manejo de excepciones. esto significa que una implementación de Python tiene un desenrollado de pila o no es Python. parece ser completamente ignorante de los temas sobre los que hace afirmaciones (fantasías). lo siento.
Saludos y hth. - Alf
2
@ Cheersandhth.-Alf: "Pyhon tiene una finalmente-cláusula para el manejo de excepciones. Esto significa que una implementación de Python tiene un desenrollado de pila o no es Python". La try..finallyconstrucción se puede implementar sin desenrollar la pila. F #, C # y Java se implementan try..finallysin usar el desenrollado de pila. Solo longjmpal manejador (como ya expliqué).
JD
4
@JonHarrop: que suena como que plantea un dilema. pero no tiene relevancia, puedo ver nada de lo discutido hasta ahora, y hasta ahora ha publicado una larga secuencia de tonterías que suenan negativas . Tendría que confiar en ti para estar de acuerdo o no con algunas palabras vagas, porque como antagonista estás eligiendo lo que revelarás que "significa", y ciertamente no confío en ti después de todas esas tonterías sin sentido, votaciones negativas, etc.
Saludos y hth. - Alf
13

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.

Benchmark de rendimiento de excepciones de C ++

Arash
fuente
12

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 throwes prácticamente un goto catchdisfraz.

Philipp
fuente
-1 re la pregunta tal como está ahora, "esto sigue siendo cierto para C ++ 98", que ciertamente no depende del compilador. Además, esta respuesta throw new Exceptiones un Java-ismo. Por regla general, nunca se deben lanzar punteros.
Saludos y hth. - Alf
1
¿El estándar 98 dicta exactamente cómo se implementarán las excepciones?
thecoshman
6
C ++ 98 es un estándar ISO, no un compilador. Hay muchos compiladores que lo implementan.
Philipp
3
@thecoshman: No. El estándar C ++ no dice nada sobre cómo se debe implementar algo (con la posible excepción de la parte de "Límites de implementación" del estándar).
In silico
2
@Insilico, entonces solo puedo sacar la conclusión lógica de que (sorprendentemente) se define la implementación (lectura, compilador específico) cómo funcionan las excepciones.
thecoshman
4

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 catchcomo else) 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)

result = attempt<lexical_cast<int>>("12345");  //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...     
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
     int x = result.get(); // or result.result;
}
else 
{
     // even possible to see what is the exception that would have happened in original function
     switch (result.exception_type())
     //...

}

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 ...

NoSenseEtAl
fuente
0

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.

Chris McCabe
fuente
9
Esta es, en el mejor de los casos, una forma muy simplista de ver el rendimiento de las excepciones. Por ejemplo, GCC utiliza una implementación de "costo cero" en la que no se produce un impacto en el rendimiento si no se producen excepciones. Y las excepciones están destinadas a circunstancias excepcionales (es decir, raras), por lo que incluso si son lentas según alguna métrica, todavía no es razón suficiente para no usarlas.
In silico
@insilico, si miras por qué dije, no dije que no use excepciones y punto. Especifiqué código de rendimiento intensivo, esta es una evaluación precisa, principalmente trabajo con gpgpus y me dispararían si usara excepciones.
Chris McCabe