reusable practices patterns net guidelines framework for convenciones coding codigo classes best and .net coding-style

.net - practices - ¿Por qué son los bloques de prueba costosos?



framework design guidelines conventions idioms and patterns for reusable.net libraries (11)

Un bloqueo de prueba no es caro en absoluto. Se incurre en poco o ningún costo a menos que se presente una excepción. Y si se ha lanzado una excepción, es una circunstancia excepcional y ya no le importa el rendimiento. ¿Importa si su programa tarda 0.001 segundos o 1.0 segundos en caerse? No, no lo hace. Lo que importa es qué tan buena es la información que se le informa a usted para que pueda solucionarla y evitar que vuelva a suceder.

He escuchado el consejo de que debes evitar intentar atrapar bloques si es posible, ya que son caros.

Mi pregunta es específicamente sobre la plataforma .NET: ¿Por qué son costosos los bloques de prueba?

Resumen de respuestas:

Claramente hay dos campos en este tema: los que dicen que los bloques de prueba son caros, y los que dicen "tal vez un poquito".

Los que dicen que los bloques de prueba son caros normalmente mencionan el "alto costo" de desenrollar la pila de llamadas. Personalmente, ese argumento no me convence, especialmente después de leer sobre cómo se almacenan los manejadores de excepciones aquí .

Jon Skeet se sienta en el campamento "tal vez un poquito" y ha escrito dos artículos sobre excepciones y desempeño que puedes encontrar aquí .

Hubo un artículo que encontré extremadamente interesante: hablaba de "otras" implicaciones de rendimiento de los bloques de prueba (no necesariamente de memoria o consumo de CPU). Peter Ritchie menciona que descubrió que el código dentro de los bloques de prueba no está optimizado como lo haría el compilador. Puedes leer sobre sus hallazgos aquí .

Finalmente, hay una entrada de blog sobre el tema del hombre que implementó excepciones en el CLR. Ve a ver el artículo de Chris Brumme aquí .


Cada intento necesita registrar mucha información, por ejemplo, apuntes de pila, valores de registro de CPU, etc. para que pueda desenrollar la pila y recuperar el estado que ha tenido al pasar el bloque de prueba en caso de que se produzca una excepción. No solo que cada intento necesita registrar mucha información, cuando se lanza una excepción, se deben restaurar muchos valores. Por lo tanto, un intento es muy costoso y un lanzamiento / captura también es muy caro.

Eso no significa que no deba usar excepciones, sin embargo, en el código de rendimiento crítico, tal vez no debería usar demasiados intentos y tampoco lanzar excepciones con demasiada frecuencia.


Dudo que sean particularmente caros. Muchas veces, son necesarios / requeridos.

Aunque recomiendo usarlos solo cuando sea necesario y en el lugar / nivel correcto de anidación en lugar de volver a lanzar la excepción en cada devolución de llamada.

Me imagino que la razón principal para el consejo fue decir que no deberías estar usando trampas donde, de lo contrario, sería un mejor enfoque.


Ligeramente O / T, pero ...

Existe un concepto de diseño bastante bueno que dice que nunca debe requerir manejo de excepciones. Esto significa simplemente que debe poder consultar cualquier objeto para cualquier condición que pueda arrojar una excepción antes de que esa excepción sea lanzada.

Como poder decir "escribible ()" antes de "escribir ()", cosas así.

Es una idea decente, y si se usa, hace que las excepciones comprobadas en Java se vean estúpidas, es decir, verificando una condición y justo después de eso, ¿se ven obligados a escribir todavía un try / catch para la misma condición?

Es un patrón bastante bueno, pero el compilador puede aplicar excepciones comprobadas, estas verificaciones no pueden. Además, no todas las bibliotecas están hechas con este patrón de diseño; es algo a tener en cuenta cuando se trata de excepciones.


No debe evitar intentar / atrapar bloques ya que generalmente significa que no está manejando adecuadamente las excepciones que puedan ocurrir. El Manejo estructurado de excepciones (SEH) solo es costoso cuando realmente ocurre una excepción ya que el tiempo de ejecución debe recorrer la pila de llamadas buscando un controlador catch, ejecutar ese controlador (y puede haber más de uno), luego ejecutar los bloques finally y luego regresar controle de nuevo al código en la ubicación correcta.

Las excepciones no se deben usar para controlar la lógica del programa, sino para indicar condiciones de error.

Una de las ideas erróneas más grandes sobre las excepciones es que son para "condiciones excepcionales". La realidad es que son para comunicar las condiciones de error. Desde una perspectiva de diseño del marco, no existe una "condición excepcional". Si una condición es excepcional o no depende del contexto de uso, pero las bibliotecas reutilizables raramente saben cómo se usarán. Por ejemplo, OutOfMemoryException podría ser excepcional para una aplicación de entrada de datos simple; no es tan excepcional para las aplicaciones que hacen su propia gestión de memoria (por ejemplo, servidor SQL). En otras palabras, la condición excepcional de un hombre es la condición crónica de otro hombre. [http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx]


No es el bloque en sí mismo el que es caro, y ni siquiera es una excepción, es caro, es el tiempo de ejecución que desenrolla la pila de llamadas hasta que encuentra un marco de pila que puede manejar la excepción. Lanzar una excepción es bastante liviano, pero si el tiempo de ejecución tiene que subir seis tramas de pila (es decir, seis llamadas de método profundo) para encontrar un manejador de excepciones apropiado, posiblemente ejecutando bloques finalmente a medida que avanza, es posible que pase una cantidad notable de tiempo .


No se trata de bloques de prueba de los que debe preocuparse tanto como bloques de captura . Y luego, no es que quiera evitar escribir los bloques: es que quiere tanto como sea posible escribir código que nunca los use.


El compilador emite más IL cuando ajusta el código dentro de un bloque try / catch; Mira, para el siguiente programa:

using System; public class Program { static void Main(string[] args) { Console.WriteLine("abc"); } }

El compilador emitirá este IL:

.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "abc" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method Program::Main

Mientras que para la versión ligeramente modificada:

using System; public class Program { static void Main(string[] args) { try { Console.WriteLine("abc"); } catch { } } }

emite más:

.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 23 (0x17) .maxstack 1 IL_0000: nop .try { IL_0001: nop IL_0002: ldstr "abc" IL_0007: call void [mscorlib]System.Console::WriteLine(string) IL_000c: nop IL_000d: nop IL_000e: leave.s IL_0015 } // end .try catch [mscorlib]System.Object { IL_0010: pop IL_0011: nop IL_0012: nop IL_0013: leave.s IL_0015 } // end handler IL_0015: nop IL_0016: ret } // end of method Program::Main

Todos estos NOP y otros cuestan.


OMI toda esta discusión es como decir "guau guau son caros porque necesito aumentar el contador ... no voy a usarlos más", o "guau crear un objeto lleva tiempo, no voy a crear una tonelada de objetos más ".

La línea de fondo es su código de adición, presumiblemente por una razón. Si las líneas de código no incurren en una sobrecarga, incluso si su ciclo de 1 CPU, ¿por qué existiría? Nada es gratis.

Lo mejor que puede hacer, como con cualquier línea de código que agregue a su aplicación, es colocarlo allí solo si lo necesita para hacer algo. Si capturar una excepción es algo que debe hacer, entonces hágalo ... como si necesitara una cuerda para almacenar algo, cree una nueva cuerda. De la misma manera, si declara una variable que no se usa alguna vez, está desperdiciando memoria y ciclos de CPU para crearla y debería eliminarse. lo mismo con un try / catch.

En otras palabras, si hay un código para hacer algo, entonces suponga que hacer algo va a consumir CPU y / o memoria de alguna manera.


Creo que la gente realmente sobreestima el costo de rendimiento de lanzar excepciones. Sí, hay un golpe de rendimiento, pero es relativamente pequeño.

Ejecuté la siguiente prueba, arrojando y atrapando un millón de excepciones. Me tomó unos 20 segundos en mi Intel Core 2 Duo , 2.8 GHz. Eso es aproximadamente 50,000 excepciones por segundo. Si estás lanzando incluso una pequeña fracción de eso, tienes algunos problemas de arquitectura.

Aquí está mi código:

using System; using System.Diagnostics; namespace Test { class Program { static void Main(string[] args) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) { try { throw new Exception(); } catch {} } Console.WriteLine(sw.ElapsedMilliseconds); Console.Read(); } } }


Esto no es algo de lo que me preocupe. Prefiero preocuparme por la claridad y la seguridad de un intento ... finalmente bloquearme sobre lo "caro" que es.

Yo personalmente no uso un 286, ni tampoco cualquiera que use .NET o Java tampoco. Siga adelante. Preocuparse por escribir un buen código que afecte a sus usuarios y a otros desarrolladores en lugar del marco subyacente que funciona bien para el 99.999999% de las personas que lo usan.

Esto probablemente no sea muy útil, y no me refiero a ser mordaz, sino solo a resaltar la perspectiva.