Pregunta

Entonces, sé que try/catch agrega algo de sobrecarga y, por lo tanto, no es una buena forma de controlar el flujo del proceso, pero ¿de dónde proviene esta sobrecarga y cuál es su impacto real?

¿Fue útil?

Solución

No soy un experto en implementaciones de lenguajes (así que tómate esto con cautela), pero creo que uno de los mayores costos es desenrollar la pila y almacenarla para el seguimiento de la misma.Sospecho que esto sucede sólo cuando se lanza la excepción (pero no lo sé), y si es así, esto sería un costo oculto de tamaño decente cada vez que se lanza una excepción...así que no es como si simplemente estuvieras saltando de un lugar del código a otro, están sucediendo muchas cosas.

No creo que sea un problema siempre que utilice excepciones para un comportamiento EXCEPCIONAL (por lo que no es la ruta típica y esperada a través del programa).

Otros consejos

Tres puntos a destacar aquí:

  • En primer lugar, hay poca o ninguna penalización en el rendimiento al tener bloques try-catch en su código.Esto no debería ser una consideración al intentar evitar tenerlos en su aplicación.El impacto en el rendimiento solo entra en juego cuando se lanza una excepción.

  • Cuando se produce una excepción además de las operaciones de desenrollado de la pila, etc. que tienen lugar y que otros han mencionado, debe tener en cuenta que suceden un montón de cosas relacionadas con el tiempo de ejecución/reflexión para completar los miembros de la clase de excepción, como el seguimiento de la pila. objeto y los distintos tipos de miembros, etc.

  • Creo que esta es una de las razones por las que el consejo general si vas a volver a lanzar la excepción es simplemente throw; en lugar de lanzar la excepción nuevamente o construir una nueva, ya que en esos casos toda la información de la pila se vuelve a recopilar, mientras que en el lanzamiento simple se conserva toda.

¿Está preguntando acerca de la sobrecarga de usar try/catch/finally cuando no se lanzan excepciones, o la sobrecarga de usar excepciones para controlar el flujo del proceso?Esto último es algo parecido a usar un cartucho de dinamita para encender la vela de cumpleaños de un niño pequeño, y los gastos generales asociados se dividen en las siguientes áreas:

  • Puede esperar errores de caché adicionales debido a la excepción lanzada al acceder a datos residentes que normalmente no están en el caché.
  • Puede esperar errores de página adicionales debido a la excepción lanzada al acceder a código no residente y a datos que normalmente no se encuentran en el conjunto de trabajo de su aplicación.

    • por ejemplo, lanzar la excepción requerirá que CLR encuentre la ubicación de los bloques finalmente y catch en función de la IP actual y la IP de retorno de cada cuadro hasta que se maneje la excepción más el bloque de filtro.
    • costo de construcción adicional y resolución de nombres para crear los marcos con fines de diagnóstico, incluida la lectura de metadatos, etc.
    • Los dos elementos anteriores normalmente acceden a códigos y datos "fríos", por lo que es probable que se produzcan fallos de página si tienes presión de memoria:

      • CLR intenta colocar el código y los datos que se usan con poca frecuencia lejos de los datos que se usan con frecuencia para mejorar la localidad, por lo que esto va en su contra porque está forzando que el frío se caliente.
      • el costo de las fallas de página, si las hay, eclipsará todo lo demás.
  • Las situaciones de captura típicas suelen ser profundas, por lo que los efectos anteriores tenderían a magnificarse (aumentando la probabilidad de errores de página).

En cuanto al impacto real del costo, esto puede variar mucho dependiendo de lo que esté sucediendo en su código en ese momento.Jon Skeet tiene un buen resumen aquí, con algunos enlaces útiles.Tiendo a estar de acuerdo con su afirmación de que si llega al punto en que las excepciones perjudican significativamente su rendimiento, tendrá problemas en términos del uso de excepciones más allá del rendimiento.

En mi experiencia, la mayor sobrecarga es generar una excepción y manejarla.Una vez trabajé en un proyecto donde se usaba un código similar al siguiente para verificar si alguien tenía derecho a editar algún objeto.Este método HasRight() se usó en todas partes de la capa de presentación y, a menudo, se invocó para cientos de objetos.

bool HasRight(string rightName, DomainObject obj) {
  try {
    CheckRight(rightName, obj);
    return true;
  }
  catch (Exception ex) {
    return false;
  }
}

void CheckRight(string rightName, DomainObject obj) {
  if (!_user.Rights.Contains(rightName))
    throw new Exception();
}

Cuando la base de datos de prueba se llenó con datos de prueba, esto provocó una desaceleración muy visible al abrir nuevos formularios, etc.

Así que lo refactoricé a lo siguiente, que, según mediciones rápidas y sucias posteriores, es aproximadamente 2 órdenes de magnitud más rápido:

bool HasRight(string rightName, DomainObject obj) {
  return _user.Rights.Contains(rightName);
}

void CheckRight(string rightName, DomainObject obj) {
  if (!HasRight(rightName, obj))
    throw new Exception();
}

En resumen, usar excepciones en el flujo de proceso normal es aproximadamente dos órdenes de magnitud más lento que usar un flujo de proceso similar sin excepciones.

Sin mencionar que si está dentro de un método llamado con frecuencia, puede afectar el comportamiento general de la aplicación.
Por ejemplo, considero el uso de Int32.Parse como una mala práctica en la mayoría de los casos, ya que genera excepciones para algo que de otra manera se puede detectar fácilmente.

Entonces para concluir todo lo escrito aquí:
1) Utilice bloques try..catch para detectar errores inesperados, casi sin penalización en el rendimiento.
2) No utilice excepciones para errores exceptuados si puede evitarlo.

Escribí un artículo sobre esto hace un tiempo porque había mucha gente preguntando sobre esto en ese momento.Puede encontrarlo y el código de prueba en http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx.

El resultado es que hay una pequeña cantidad de sobrecarga para un bloque try/catch, pero tan pequeña que debe ignorarse.Sin embargo, si está ejecutando bloques try/catch en bucles que se ejecutan millones de veces, puede considerar mover el bloque fuera del bucle si es posible.

El problema clave de rendimiento con los bloques try/catch es cuando realmente se detecta una excepción.Esto puede agregar un retraso notable a su solicitud.Por supuesto, cuando las cosas van mal, la mayoría de los desarrolladores (y muchos usuarios) reconocen la pausa como una excepción que está a punto de ocurrir.La clave aquí es no utilizar el manejo de excepciones para operaciones normales.Como sugiere el nombre, son excepcionales y debes hacer todo lo posible para evitar que los arrojen.No debe utilizarlos como parte del flujo esperado de un programa que funciona correctamente.

Hice una Entrada de blog sobre este tema el año pasado.Échale un vistazo.La conclusión es que casi no hay costo por un bloque de prueba si no ocurre ninguna excepción, y en mi computadora portátil, una excepción fue de aproximadamente 36 μs.Puede que sea menos de lo que esperabas, pero ten en cuenta que esos resultados fueron poco profundos.Además, las primeras excepciones son realmente lentas.

Es mucho más fácil escribir, depurar y mantener código que esté libre de mensajes de error del compilador, mensajes de advertencia de análisis de código y excepciones rutinarias aceptadas (particularmente excepciones que se lanzan en un lugar y se aceptan en otro).Debido a que es más fácil, el código, en promedio, estará mejor escrito y tendrá menos errores.

Para mí, esa sobrecarga de programación y calidad es el principal argumento en contra del uso de try-catch para el flujo de procesos.

La sobrecarga informática de las excepciones es insignificante en comparación y, por lo general, pequeña en términos de la capacidad de la aplicación para cumplir con los requisitos de rendimiento del mundo real.

Contrariamente a las teorías comúnmente aceptadas, try/catch puede tener importantes implicaciones en el rendimiento, ¡y eso depende de si se lanza o no una excepción!

  1. Deshabilita algunas optimizaciones automáticas (por diseño), y en algunos casos se inyecta depuración código, como se puede esperar de un ayuda de depuración.Siempre habrá gente que no esté de acuerdo conmigo en este punto, pero el lenguaje lo requiere y el desmontaje lo muestra, por lo que esas personas son según la definición del diccionario. delirante.
  2. Puede afectar negativamente al mantenimiento. Este es en realidad el problema más importante aquí, pero como se eliminó mi última respuesta (que se centró casi por completo en él), intentaré centrarme en el problema menos importante (la microoptimización) en lugar del problema más importante ( la macrooptimización).

El primero ha sido cubierto en un par de publicaciones de blog de los MVP de Microsoft a lo largo de los años, y confío en que puedas encontrarlos fácilmente, pero a StackOverflow le importa. mucho acerca de contenido así que proporcionaré enlaces a algunos de ellos como relleno evidencia:

También hay esta respuesta que muestra la diferencia entre código desensamblado con y sin uso try/catch.

Parece tan obvio que hay es una sobrecarga que es descaradamente observable en la generación de código, y esa sobrecarga incluso parece ser reconocida por personas que Microsoft valora.Sin embargo, lo soy, repitiendo internet...

Sí, hay docenas de instrucciones MSIL adicionales para una línea trivial de código, y eso ni siquiera cubre las optimizaciones deshabilitadas, por lo que técnicamente es una microoptimización.


Publiqué una respuesta hace años que se eliminó porque se centraba en la productividad de los programadores (la macrooptimización).

Esto es desafortunado ya que ningún ahorro de unos pocos nanosegundos aquí y allá de tiempo de CPU probablemente compensará muchas horas acumuladas de optimización manual por parte de los humanos.¿Por qué paga más tu jefe?¿una hora de tu tiempo o una hora con la computadora encendida?¿En qué momento desconectamos y admitimos que es hora de simplemente comprar una computadora más rápida?

Claramente, deberíamos ser optimizando nuestras prioridades, ¡No solo nuestro código!En mi última respuesta me basé en las diferencias entre dos fragmentos de código.

Usando try/catch:

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

No usando try/catch:

int x;
if (int.TryParse("1234", out x) == false) {
    return;
}
// some more code here

Considere desde la perspectiva de un desarrollador de mantenimiento, qué es más probable que le haga perder el tiempo, si no es en la creación de perfiles/optimización (tratada anteriormente), que probablemente ni siquiera sería necesaria si no fuera por la try/catch problema, luego al desplazarse por el código fuente...¡Uno de ellos tiene cuatro líneas adicionales de basura estándar!

A medida que se introducen más y más campos en una clase, toda esta basura repetitiva se acumula (tanto en el código fuente como en el código desensamblado) mucho más allá de los niveles razonables.Cuatro líneas adicionales por campo, y siempre son las mismas líneas...¿No nos enseñaron a evitar repetirnos?Supongo que podríamos ocultar el try/catch detrás de alguna abstracción casera, pero...entonces también podríamos evitar las excepciones (es decir,usar Int.TryParse).

Este ni siquiera es un ejemplo complejo;He visto intentos de crear instancias de nuevas clases en try/catch.Tenga en cuenta que todo el código dentro del constructor podría quedar descalificado de ciertas optimizaciones que, de otro modo, el compilador aplicaría automáticamente.¿Qué mejor manera de dar lugar a la teoría de que el compilador es lento, Opuesto a el compilador está haciendo exactamente lo que se le dice que haga?

Suponiendo que dicho constructor lanza una excepción y, como resultado, se activa algún error, el desarrollador con mantenimiento deficiente tiene que rastrearlo.Puede que no sea una tarea tan fácil, ya que, a diferencia del código espagueti del ir a pesadilla, try/catch puede causar problemas en tres dimensiones, ya que podría ascender en la pila no solo a otras partes del mismo método, sino también a otras clases y métodos, todos los cuales serán observados por el desarrollador de mantenimiento, el camino difícil!Sin embargo, nos dicen que "goto es peligroso", ¡je!

Al final menciono, try/catch tiene su beneficio que es, está diseñado para desactivar las optimizaciones!Es, por así decirlo, un ayuda de depuración!Para eso fue diseñado y para eso debe usarse...

Supongo que ese también es un punto positivo.Se puede usar para deshabilitar optimizaciones que de otro modo podrían paralizar algoritmos de paso de mensajes seguros y sensatos para aplicaciones multiproceso, y para detectar posibles condiciones de carrera;) Ese es el único escenario que se me ocurre para usar try/catch.Incluso eso tiene alternativas.


¿Qué hacen las optimizaciones? try, catch y finally ¿desactivar?

También conocido como

Cómo están try, catch y finally ¿Útil como ayuda para la depuración?

son barreras de escritura.Esto viene del estándar:

12.3.3.13 Declaraciones try-catch

para una declaración stmt de la forma:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
  • El estado de asignación definitivo de v al comienzo de bloque de prueba es el mismo que el estado de asignación definido de v al comienzo de stmt.
  • El estado de asignación definitivo de v al comienzo de atrapar-bloque-i (para cualquier i) es el mismo que el estado de asignación definido de v al comienzo de stmt.
  • El estado de asignación definitivo de v en el punto final de stmt se asigna definitivamente si (y sólo si) v está definitivamente asignado en el punto final de bloque de prueba y cada atrapar-bloque-i (para cada i de 1 a norte).

En otras palabras, al comienzo de cada try declaración:

  • todas las asignaciones realizadas a objetos visibles antes de entrar en el try La declaración debe estar completa, lo que requiere un bloqueo de subprocesos para comenzar, lo que la hace útil para depurar las condiciones de carrera.
  • el compilador no puede:
    • eliminar asignaciones de variables no utilizadas que definitivamente han sido asignadas antes de la try declaración
    • reorganizar o fusionar cualquiera de sus tareas internas (es decir.mira mi primer enlace, si aún no lo has hecho).
    • elevar las asignaciones por encima de esta barrera, para retrasar la asignación a una variable que sabe que no se usará hasta más tarde (si es que se usará) o para adelantar de manera preventiva asignaciones posteriores para hacer posibles otras optimizaciones...

Una historia similar es válida para cada catch declaración;supongamos dentro de tu try declaración (o un constructor o función que invoca, etc.) que asigna a esa variable que de otro modo sería inútil (digamos, garbage=42;), el compilador no puede eliminar esa declaración, sin importar cuán irrelevante sea para el comportamiento observable del programa.La tarea debe tener terminado antes de catch Se ingresa al bloque.

Por lo que vale, finally dice algo similar degradante historia:

12.3.3.14 Declaraciones de prueba final

Para intentar declaración stmt de la forma:

try try-block
finally finally-block

• El estado de asignación definitivo de v al comienzo de bloque de prueba es el mismo que el estado de asignación definido de v al comienzo de stmt.
• El estado de asignación definitivo de v al comienzo de finalmente bloquear es el mismo que el estado de asignación definido de v al comienzo de stmt.
• El estado de asignación definitivo de v en el punto final de stmt se asigna definitivamente si (y sólo si):oh v está definitivamente asignado en el punto final de bloque de pruebaoh v está definitivamente asignado en el punto final de finalmente bloquearSi una transferencia de flujo de control (como una ir a declaración) que comienza dentro de bloque de prueba, y termina fuera de bloque de prueba, entonces v también se considera definitivamente asignado en esa transferencia de flujo de control si v está definitivamente asignado en el punto final de finalmente bloquear.(Esto no es sólo si—si v está asignado definitivamente por otro motivo en esta transferencia de flujo de control, entonces todavía se considera asignado definitivamente).

12.3.3.15 Declaraciones try-catch-finally

Análisis de asignación definitiva para un intentar-atrapar-finalmente declaración de la forma:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
finally finally-block

se hace como si la declaración fuera una intentar-finalmente declaración que incluye un intentar-atrapar declaración:

try { 
    try   
    try-block
    catch ( ... ) catch-block-1
    ...   
    catch ( ... ) catch-block-n
} 
finally finally-block

Me gusta mucho el de Hafthor entrada en el blog, y para agregar mi granito de arena a esta discusión, me gustaría decir que siempre ha sido fácil para mí hacer que la CAPA DE DATOS arroje solo un tipo de excepción (DataAccessException).De esta manera mi CAPA EMPRESARIAL sabe qué excepción esperar y la detecta.Luego, dependiendo de otras reglas comerciales (es decir,si mi objeto comercial participa en el flujo de trabajo, etc.), puedo generar una nueva excepción (BusinessObjectException) o continuar sin volver a lanzarla.

Yo diría que no dudes en usar try..catch siempre que sea necesario y usarlo sabiamente.

Por ejemplo, este método participa en un flujo de trabajo...

¿Comentarios?

public bool DeleteGallery(int id)
{
    try
    {
        using (var transaction = new DbTransactionManager())
        {
            try
            {
                transaction.BeginTransaction();

                _galleryRepository.DeleteGallery(id, transaction);
                _galleryRepository.DeletePictures(id, transaction);

                FileManager.DeleteAll(id);

                transaction.Commit();
            }
            catch (DataAccessException ex)
            {
                Logger.Log(ex);
                transaction.Rollback();                        
                throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
            }
        }
    }
    catch (DbTransactionException ex)
    {
        Logger.Log(ex);
        throw new BusinessObjectException("Cannot delete gallery.", ex);
    }
    return true;
}

Podemos leer en Pragmática de lenguajes de programación de Michael L.Scott que los compiladores actuales no añaden ninguna sobrecarga en los casos comunes, es decir, cuando no se producen excepciones.Entonces cada trabajo se realiza en tiempo de compilación.Pero cuando se genera una excepción en tiempo de ejecución, el compilador necesita realizar una búsqueda binaria para encontrar la excepción correcta y esto sucederá con cada nuevo lanzamiento que realice.

Pero las excepciones son excepciones y este coste es perfectamente aceptable.Si intenta realizar un manejo de excepciones sin excepciones y utiliza códigos de error de retorno, probablemente necesitará una declaración if para cada subrutina y esto generará una sobrecarga de tiempo real.Usted sabe que una declaración if se convierte en algunas instrucciones de ensamblaje, que se ejecutarán cada vez que ingrese a sus subrutinas.

Perdón por mi inglés, espero que te ayude.Esta información se basa en el libro citado; para obtener más información, consulte el Capítulo 8.5 Manejo de excepciones.

Analicemos uno de los mayores costos posibles de un bloque try/catch cuando se usa donde no debería ser necesario:

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

Y aquí está el que no tiene try/catch:

int x;
if (int.TryParse("1234", out x) == false) {
    return;
}
// some more code here

Sin contar los insignificantes espacios en blanco, uno podría notar que estas dos piezas de código equivalentes tienen casi exactamente la misma longitud en bytes.Este último contiene 4 bytes menos de sangría.¿Es eso algo malo?

Para colmo de males, un estudiante decide realizar un bucle mientras la entrada se puede analizar como un int.La solución sin try/catch podría ser algo como:

while (int.TryParse(...))
{
    ...
}

Pero, ¿cómo se ve esto cuando se usa try/catch?

try {
    for (;;)
    {
        x = int.Parse(...);
        ...
    }
}
catch
{
    ...
}

Los bloques de prueba/captura son formas mágicas de desperdiciar sangría, ¡y todavía ni siquiera sabemos la razón por la que fallaron!Imagínese cómo se siente la persona que realiza la depuración cuando el código continúa ejecutándose más allá de una falla lógica grave, en lugar de detenerse con un error de excepción obvio y agradable.Los bloques try/catch son una validación/saneamiento de datos para un hombre perezoso.

Uno de los costos más pequeños es que los bloques try/catch deshabilitan ciertas optimizaciones: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx.Supongo que ese también es un punto positivo.Se puede usar para deshabilitar optimizaciones que de otro modo podrían paralizar algoritmos de paso de mensajes seguros y sensatos para aplicaciones multiproceso, y para detectar posibles condiciones de carrera;) Ese es el único escenario que se me ocurre para usar try/catch.Incluso eso tiene alternativas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top