Sigo viendo que la gente dice que las excepciones son lentas, pero nunca veo ninguna prueba. Entonces, en lugar de preguntar si lo son, preguntaré cómo funcionan las excepciones entre bastidores, para poder tomar decisiones sobre cuándo usarlas y si son lentas.
Por lo que sé, las excepciones son lo mismo que hacer una devolución varias veces, excepto que también verifica después de cada devolución si necesita hacer otra o detenerse. ¿Cómo comprueba cuándo dejar de devolver? Supongo que hay una segunda pila que contiene el tipo de excepción y una ubicación de la pila, luego regresa hasta que llega allí. También supongo que la única vez que se toca esta segunda pila es en un lanzamiento y en cada intento / recepción. AFAICT implementar un comportamiento similar con códigos de retorno tomaría la misma cantidad de tiempo. Pero todo esto es solo una suposición, así que quiero saber qué sucede realmente.
¿Cómo funcionan realmente las excepciones?
Respuestas:
En lugar de adivinar, decidí mirar el código generado con un pequeño fragmento de código C ++ y una instalación de Linux algo antigua.
Lo compilé
g++ -m32 -W -Wall -O3 -save-temps -c
y miré el archivo de ensamblaje generado._ZN11MyExceptionD1Ev
es decirMyException::~MyException()
, el compilador decidió que necesitaba una copia no en línea del destructor.¡Sorpresa! No hay instrucciones adicionales en la ruta del código normal. En cambio, el compilador generó bloques de código de reparación extra fuera de línea, referenciados mediante una tabla al final de la función (que en realidad se coloca en una sección separada del ejecutable). Todo el trabajo se realiza entre bastidores mediante la biblioteca estándar, basada en estas tablas (
_ZTI11MyException
istypeinfo for MyException
).De acuerdo, eso no fue una sorpresa para mí, ya sabía cómo lo hacía este compilador. Continuando con la salida de montaje:
Aquí vemos el código para lanzar una excepción. Si bien no hubo una sobrecarga adicional simplemente porque podría lanzarse una excepción, obviamente hay una gran sobrecarga al lanzar y capturar una excepción. La mayor parte está oculta dentro
__cxa_throw
, que debe:Compare eso con el costo de simplemente devolver un valor, y verá por qué las excepciones deben usarse solo para devoluciones excepcionales.
Para terminar, el resto del archivo de ensamblaje:
Los datos de typeinfo.
Incluso más tablas de manejo de excepciones e información adicional variada.
Entonces, la conclusión, al menos para GCC en Linux: el costo es espacio adicional (para los controladores y tablas) independientemente de si se lanzan o no excepciones, más el costo adicional de analizar las tablas y ejecutar los controladores cuando se lanza una excepción. Si usa excepciones en lugar de códigos de error, y un error es poco común, puede ser más rápido , ya que ya no tiene la sobrecarga de probar los errores.
En caso de que desee obtener más información, en particular qué hacen todas las
__cxa_
funciones, consulte la especificación original de la que provienen:fuente
Excepciones siendo lenta era cierto en los viejos tiempos.
En la mayoría de los compiladores modernos, esto ya no es cierto.
Nota: El hecho de que tengamos excepciones no significa que no usemos códigos de error también. Cuando el error se pueda manejar localmente, use códigos de error. Cuando los errores requieren más contexto para la corrección, use excepciones: Lo escribí con mucha más elocuencia aquí: ¿Cuáles son los principios que guían su política de manejo de excepciones?
El costo del código de manejo de excepciones cuando no se utilizan excepciones es prácticamente cero.
Cuando se lanza una excepción, se realiza algún trabajo.
Pero debe comparar esto con el costo de devolver los códigos de error y verificarlos hasta el punto donde se puede manejar el error. Ambos requieren más tiempo para escribir y mantener.
También hay un problema para los principiantes:
aunque se supone que los objetos de excepción son pequeños, algunas personas ponen muchas cosas dentro de ellos. Entonces tienes el costo de copiar el objeto de excepción. La solución es doble:
En mi opinión, apostaría a que el mismo código con excepciones es más eficiente o al menos tan comparable como el código sin las excepciones (pero tiene todo el código adicional para verificar los resultados del error de función). Recuerde que no obtiene nada gratis, el compilador está generando el código que debería haber escrito en primer lugar para verificar los códigos de error (y generalmente el compilador es mucho más eficiente que un humano).
fuente
Hay varias formas de implementar excepciones, pero normalmente dependerán de algún soporte subyacente del sistema operativo. En Windows, este es el mecanismo estructurado de manejo de excepciones.
Hay una discusión decente de los detalles en Code Project: cómo un compilador de C ++ implementa el manejo de excepciones
La sobrecarga de las excepciones se produce porque el compilador tiene que generar código para realizar un seguimiento de qué objetos deben destruirse en cada marco de pila (o más precisamente en el ámbito) si una excepción se propaga fuera de ese ámbito. Si una función no tiene variables locales en la pila que requieran la llamada de destructores, entonces no debería tener una penalización de rendimiento con el manejo de excepciones.
El uso de un código de retorno solo puede desenrollar un solo nivel de la pila a la vez, mientras que un mecanismo de manejo de excepciones puede retroceder mucho más en la pila en una operación si no hay nada que hacer en los marcos de pila intermedios.
fuente
Matt Pietrek escribió un excelente artículo sobre el manejo estructurado de excepciones de Win32 . Si bien este artículo se escribió originalmente en 1997, todavía se aplica hoy (pero, por supuesto, solo se aplica a Windows).
fuente
Este artículo examina el problema y básicamente descubre que, en la práctica, las excepciones tienen un costo de tiempo de ejecución, aunque el costo es bastante bajo si no se lanza la excepción. Buen artículo, recomendado.
fuente
Un amigo mío escribió un poco cómo Visual C ++ maneja las excepciones hace algunos años.
http://www.xyzw.de/c160.html
fuente
Todas buenas respuestas.
Además, piense en lo más fácil que es depurar el código que hace "si verifica" como puertas en la parte superior de los métodos en lugar de permitir que el código arroje excepciones.
Mi lema es que es fácil escribir código que funcione. Lo más importante es escribir el código para la próxima persona que lo vea. En algunos casos, serás tú en 9 meses y no querrás maldecir tu nombre.
fuente