¿Las excepciones en C++ son realmente lentas?

¿Las excepciones en C++ son realmente lentas?

El modelo principal que se utiliza hoy en día para las excepciones (Itanium ABI, VC++ de 64 bits) es el modelo de excepciones de coste cero.

La idea es que en lugar de perder tiempo configurando un guardia y verificando explícitamente la presencia de excepciones en todas partes, el compilador genera una tabla lateral que asigna cualquier punto que pueda generar una excepción (Contador de programa) a la lista de controladores. Cuando se lanza una excepción, se consulta esta lista para elegir el controlador correcto (si lo hay) y la pila se deshace.

Comparado con el típico if (error) estrategia:

  • el modelo de Costo Cero, como su nombre lo indica, es gratuito cuando no ocurren excepciones
  • cuesta alrededor de 10x/20x un if cuando ocurre una excepción

El costo, sin embargo, no es trivial de medir:

  • La mesa auxiliar suele estar fría , por lo que recuperarlo de la memoria lleva mucho tiempo
  • Determinar el controlador correcto involucra RTTI:muchos descriptores RTTI para obtener, dispersos en la memoria y operaciones complejas para ejecutar (básicamente un dynamic_cast prueba para cada manejador)

Por lo tanto, la mayoría de las fallas en la memoria caché y, por lo tanto, no son triviales en comparación con el código de CPU puro.

Nota:para obtener 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ápidos que las comprobaciones 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 optimización en ciertas condiciones.

¿Importa?

Yo diría que no. Un programa debe estar escrito con legibilidad en mente, no el rendimiento (al menos, no como primer criterio). Se deben usar excepciones cuando se espera que la persona que llama no pueda o no quiera manejar la falla en el lugar y pasarla a la pila. Bonificación:en C++11, las excepciones se pueden ordenar entre subprocesos mediante la biblioteca estándar.

Sin embargo, esto es sutil, afirmo que map::find no debería tirar pero estoy bien con map::find devolviendo un checked_ptr que arroja si un intento de eliminar la referencia falla porque es nulo:en el último caso, como en el caso de la clase que introdujo Alexandrescu, la persona que llama elige entre verificación explícita y confiar en excepciones. Empoderar a la persona que llama sin darle más responsabilidad suele ser una señal de buen diseño.


Cuando se publicó la pregunta, me dirigía al médico, con un taxi esperando, así que solo tuve tiempo para un breve comentario. Pero después de haber 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?

Sobre el reclamo

Si eso es literalmente lo que afirma Andrei, entonces, por una vez, es muy engañoso, si no completamente equivocado. Las excepciones lanzadas/provocadas siempre son lentas 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 funciones básicas del lenguaje que son mucho 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.

Felizmente, en C++ a menudo se pueden evitar ambos en 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. :-) La eficiencia obtenida al evitar el lanzamiento de excepciones y la asignación dinámica de memoria generalmente se logra mediante la codificación a un nivel más bajo de abstracción, usando C++ como solo un "mejor C". Y menor abstracción significa 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++ uno puede, si así lo desea, intercambiar algo de eficiencia del programador por eficiencia de ejecución. Hacerlo es en gran parte una decisión de ingeniería y de intuición, porque en la práctica solo se puede estimar y medir fácilmente la ganancia, no el costo.

¿Existen medidas objetivas del rendimiento de generación 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"?

Principalmente significa que un throw puede tomar un Very Long Time™ en comparación con p. un int asignación, debido a la búsqueda de controlador.

Como se explica en TR18015 en su sección 5.4 "Excepciones", existen dos estrategias principales de implementación del manejo de excepciones,

  • el enfoque donde cada try -block 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 el entorno 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.

Principalmente, con el enfoque del controlador dinámico (Windows de 32 bits), el manejo de excepciones tiene un impacto en try bloques, en su mayoría independientemente del idioma (porque esto es forzado por el Manejo estructurado de excepciones de Windows esquema), mientras que el enfoque de tabla estática tiene un costo prácticamente nulo para try -bloques. Discutir esto requerirí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 fechado a fines de 2012 y, que yo sepa, no hay nada comparable que sea más nuevo.

Otra perspectiva importante es que el impacto del uso de excepciones en el rendimiento es muy diferente de la eficiencia aislada de las características de soporte del idioma porque, como señala el informe,

Por ejemplo:

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

  • Sitio de llamada redundante if comprobación de fallos frente a try centralizado

  • Problemas de almacenamiento en caché (por ejemplo, un código más corto puede caber en caché)

El informe tiene una lista diferente de aspectos a considerar, pero de todos modos, la única forma práctica de obtener datos concretos sobre la eficiencia de la ejecución es probablemente implementar el mismo programa utilizando excepciones y sin excepciones, dentro de un límite determinado en el tiempo de desarrollo y con los desarrolladores. familiarizado con cada forma y luego MEDIDA .

¿Cuál es una buena manera de evitar la sobrecarga de las excepciones?

Corrección casi siempre triunfa sobre la eficiencia.

Sin excepciones, lo siguiente puede suceder fácilmente:

  1. Algún código P está destinado a obtener un recurso o calcular alguna información.

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

  3. Se utiliza un recurso inexistente o información no válida en el código que sigue a C, lo que provoca un caos general.

El principal problema es el punto (2), donde con el habitual código de retorno esquema el código de llamada C no está obligado a comprobar.

Hay dos enfoques principales que fuerzan dicha verificación:

  • Donde P arroja 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++:Introducción con técnicas avanzadas y ejemplos, donde introdujeron una clase llamada Fallow para un resultado de función "posible". Una clase similar llamada optional ahora es ofrecido por la biblioteca Boost. Y puedes implementar fácilmente un Optional clase usted mismo, usando un std::vector como portador de valor para el caso de resultado no POD.

Con el primer enfoque, el código de llamada C no tiene más remedio que usar técnicas de manejo de excepciones. Sin embargo, con el segundo enfoque, el código de llamada C puede decidir por sí mismo si hacer if verificación basada en, o manejo general de excepciones. Por lo tanto, el segundo enfoque admite hacer que el programador se compense con la eficiencia del tiempo de ejecución.

¿Cuál es el impacto de los distintos estándares de C++ en el rendimiento excepcional?

C++98 fue el primer estándar de C++. Para las excepciones, introdujo una jerarquía estándar de clases de excepción (desafortunadamente bastante imperfecta). El principal impacto en el rendimiento fue la posibilidad de especificaciones de excepción (eliminado en C++11), que sin embargo nunca fueron completamente implementados 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 las especificaciones de excepción.

C++03 era solo un corrigendum técnico de C++98. Lo único realmente nuevo en C++03 fue la inicialización de valores . Lo cual no tiene nada que ver con las excepciones.

Con el estándar C++ 11, se eliminaron las especificaciones de excepciones generales y se reemplazaron con 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 del 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 se puede usar más fácilmente en ambos lados de una devolución de llamada en lenguaje C.


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 que nunca se produzcan. Una vez que los tiras, comienza la miseria. En este ejemplo, se lanza para el 0%, 1%, 10%, 50% y 90% de los casos. Cuando las excepciones se lanzan el 90 % de las veces, el código es 8 veces más lento que en el caso en que las excepciones se lanzan el 10 % de las veces. Como ves, las excepciones son realmente lentas. No los utilice si se tiran con frecuencia. Si su aplicación no tiene requisitos de tiempo real, siéntase libre de 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.