Es, finalmente, costoso

14

En el caso de un código en el que debe realizar una limpieza de recursos antes de salir de una función, ¿existe una diferencia de rendimiento importante entre estas 2 formas de hacerlo?

  1. Limpieza del recurso antes de cada declaración de devolución

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Limpiar el recurso en un bloque finalmente

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

Hice algunas pruebas básicas en programas de muestra y no parece haber mucha diferencia. Prefiero mucho la finallyforma de hacer esto, pero me preguntaba si causará algún impacto en el rendimiento en un gran proyecto.

usuario93353
fuente
3
Realmente no vale la pena publicarlo como respuesta, pero no. Si está utilizando Java, que espera que las excepciones sean la estrategia predeterminada de manejo de errores, entonces debe usar try / catch / finally: el lenguaje está optimizado para el patrón que está utilizando.
Jonathan Rich
1
@ Jonathan Jonathan: ¿cómo es eso?
haylem
2
Escriba el código limpiamente de tal manera que exprese lo que está tratando de lograr. Optimice más adelante cuando sepa que tiene un problema : no evite las características del lenguaje porque cree que podría ahorrar unos milisegundos en algo que no es lento en primer lugar.
Sean McSomething
@mikeTheLiar: si quieres decir que debería escribir if(!cond), entonces es java lo que me hizo hacer esto. En C ++, así es como escribo código de booleanos y también para otros tipos, es decir int x; if(!x). Como java me permite usar esto solo para booleans, he dejado de usar if(cond)& if(!cond)en java.
user93353
1
@ user93353 que me pone triste. Prefiero ver (someIntValue != 0)que comparar en lugar de evaluar booleanos. Eso me huele, y lo refactorizo ​​inmediatamente cuando lo veo en la naturaleza.
MikeTheLiar

Respuestas:

23

Como se indica en ¿Qué tan lentas son las excepciones de Java? se puede ver que la lentitud try {} catch {}está dentro de la instauración de la excepción misma.

Crear una excepción obtendrá toda la pila de llamadas del tiempo de ejecución y aquí es donde está el gasto. Si no está creando una excepción, esto es solo un aumento de tiempo muy leve.

En el ejemplo dado en esta pregunta no hay excepciones, uno no esperaría ninguna desaceleración al crearlas, no se crean. En cambio, lo que está aquí es try {} finally {}manejar la desasignación de recursos dentro del bloque finalmente.

Entonces, para responder a la pregunta, no, no hay un gasto real de tiempo de ejecución dentro de una try {} finally {}estructura que no use excepciones (como se ve). Lo que posiblemente sea costoso es el tiempo de mantenimiento cuando uno lee el código y ve que este estilo de código no es típico y el codificador tiene que pensar que algo más sucede en este método después de returnantes de regresar a la llamada anterior.


Como se ha mencionado, el mantenimiento es un argumento para ambas formas de hacerlo. Para el registro, después de considerarlo, mi preferencia sería el enfoque final.

Considere el tiempo de mantenimiento de enseñarle a alguien una nueva estructura de lenguaje. Ver try {} finally {}no es algo que a menudo se ve en el código Java y, por lo tanto, puede ser confuso para las personas. Hay un grado de tiempo de mantenimiento para aprender estructuras un poco más avanzadas en Java que las que la gente conoce.

El finally {}bloque siempre corre. Y es por eso que deberías usarlo. Considere también el tiempo de mantenimiento de la depuración del enfoque no definitivo cuando alguien olvida incluir un cierre de sesión en el momento adecuado, o lo llama en el momento incorrecto, u olvida regresar / salir después de llamarlo para que se llame dos veces. Hay tantos errores posibles con esto que el uso de try {} finally {}hace imposible tenerlos.

Al sopesar estos dos costos, es costoso en tiempo de mantenimiento no utilizar el try {} finally {}enfoque. Si bien las personas pueden analizar cuántas milisegundos fraccionales o instrucciones jvm adicionales try {} finally {}se compara el bloque con la otra versión, también se debe considerar las horas dedicadas a la depuración de la forma menos que ideal de abordar la desasignación de recursos.

Escriba primero el código que se pueda mantener, y preferiblemente de una manera que evite que los errores se escriban más tarde.

Comunidad
fuente
1
Una edición no sugerida decía así: "probar / finalmente ofrece garantías que de otro modo no tendrías con respecto a las excepciones de tiempo de ejecución. Te guste o no, cualquier línea es virtualmente capaz de lanzar una excepción" - Yo: lo cual no es exactamente cierto . La estructura dada en la pregunta no tiene excepciones adicionales que ralentizarían el rendimiento del código. No hay impactos de rendimiento adicionales al envolver un bloque try / finally alrededor del bloque en comparación con el bloque no try / finally.
2
Lo que podría ser aún más costoso en mantenimiento que comprender este uso de try-finally, es tener que mantener varios bloques de limpieza. Ahora al menos sabe que si se necesita una limpieza adicional, solo hay un lugar para colocarlo.
Bart van Ingen Schenau
@BartvanIngenSchenau De hecho. Probablemente, un intento final es la mejor manera de manejarlo, simplemente no es una estructura tan común como la que he visto en el código: las personas tienen suficientes problemas para tratar de entender el intento de captura finalmente y el manejo de excepciones en general ... pero intente -finalmente sin una trampa? ¿Qué? La gente realmente no lo entiende . Como sugirió, es mejor comprender la estructura del lenguaje que tratar de evitarla y hacer las cosas mal.
Principalmente quería dar a conocer que la alternativa para probar, finalmente, tampoco es gratuita. Ojalá pudiera dar otro voto positivo por su adición sobre los costos.
Bart van Ingen Schenau
5

/programming/299068/how-slow-are-java-exceptions

La respuesta aceptada en esta pregunta muestra que envolver una llamada de función en un bloque try catch cuesta menos del 5% sobre una llamada de función simple. En realidad, lanzar y capturar la excepción causó que el tiempo de ejecución se disparara a más de 66 veces la llamada a la función desnuda. Entonces, si espera que el diseño de excepción se lance regularmente, trataría de evitarlo (en un código crítico de rendimiento correctamente perfilado), sin embargo, si la situación excepcional es rara, entonces no es un gran problema.

metal de piedra
fuente
3
"si la situación excepcional es rara" , bueno, hay una tautología allí mismo. Excepcional significa raro y así es precisamente como se deben usar las excepciones. Nunca debe ser parte del diseño o control de flujo.
Jesse C. Slicer
1
Las excepciones no son necesariamente raras en la práctica, es solo un efecto secundario accidental. Por ejemplo, si tiene una mala interfaz de usuario, las personas pueden causar que el caso de uso de excepción fluya con más frecuencia que el flujo normal
Esailija el
2
No hay excepciones en el código de la pregunta.
2
@ JesseC.Slicer Hay idiomas en los que no es raro verlos utilizados como control de flujo. Como ejemplo al iterar cualquier cosa en python, el iterador lanza una excepción de stopiteration cuando se hace. También en OCaml, su biblioteca estándar parece ser muy "feliz", arroja una gran cantidad de situaciones que no son raras (si tiene un mapa e intenta buscar algo que no existe en el mapa que arroja) )
stonemetal
@stonemetal Como lo hace un dict de Python, con un KeyError
Izkata
4

En lugar de optimizar, piense en lo que está haciendo su código.

Agregar el último bloque es una implementación diferente: significa que cerrará sesión en cada excepción.

Específicamente: si el inicio de sesión arroja una excepción, el comportamiento en su segunda función será diferente de la primera.

Si el inicio de sesión y el cierre de sesión no son realmente parte del comportamiento de la función, sugeriría que el método debería hacer una cosa y una cosa bien:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

y debe estar en una función que ya esté encapsulada en un contexto que administre el inicio / cierre de sesión, ya que estos parecen fundamentalmente diferentes de lo que está haciendo su código principal.

Su publicación está etiquetada como Java con la que no estoy muy familiarizado, pero en .net usaría un bloque de uso para esto:

es decir:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

Y el contexto de inicio de sesión tiene un método Dispose que cerrará sesión y limpiará los recursos.

Editar: ¡en realidad no respondí la pregunta!

No tengo evidencia medible, pero no esperaría que esto sea más costoso o ciertamente no lo suficientemente costoso como para ser digno de una optimización prematura.

Voy a dejar el resto de mi respuesta porque, aunque no responda la pregunta, creo que es útil para el OP.

Miguel
fuente
1
Para completar su respuesta, Java 7 introdujo una declaración de prueba con recursos que sería similar a su usingconstrucción .net , suponiendo que el recurso en cuestión implemente la AutoCloseableinterfaz.
haylem
Incluso eliminaría esta retvariable, que solo se asigna dos veces para que se verifique una línea más tarde. Hacerlo if (dosomething() == false) return;.
ott--
Acepté pero quería mantener el código OP de la forma en que se proporcionó.
Michael
@ ott: supongo que el código del OP es una simplificación de su caso de uso; por ejemplo, podrían tener algo más parecido ret2 = doSomethingElse(ret1), pero eso no es relevante para la pregunta, por lo que se eliminó.
Izkata
if (dosomething() == false) ...Es un mal código. Use el más intuitivoif (!dosomething()) ...
Steffen Heil
1

Suposición: está desarrollando en código C #.

La respuesta rápida es que no hay un impacto significativo en el rendimiento al usar los bloques try / finally. Hay un golpe de rendimiento cuando lanzas y atrapas excepciones.

La respuesta más larga es ver por ti mismo. Si observa el código CIL subyacente generado ( por ejemplo, el reflector de puerta roja, puede ver las instrucciones CIL subyacentes generadas y ver el impacto de esa manera.

Michael Shaw
fuente
11
La pregunta está etiquetada java .
Joachim Sauer
¡Bien descrito! ;)
Michael Shaw
3
Además, los métodos no se capitalizan, aunque eso podría ser solo buen gusto.
Erik Reppen
sigue siendo una buena respuesta si la pregunta era c # :)
PhillyNJ
-2

Como otros ya se ha señalado, el código Ttese tendrá resultados diferentes, si cualquiera dosomethingelseo dootherstufflanzar una excepción.

Y sí, cualquier llamada de función puede arrojar una excepción, al menos un StackOVerflowError! Si bien es poco probable manejarlos correctamente (ya que cualquier función llamada limpieza probablemente tenga el mismo problema, en algunos casos (como implementar bloqueos, por ejemplo) es crucial incluso manejar eso correctamente ...

Steffen Heil
fuente
2
esto no parece ofrecer nada sustancial sobre las 6 respuestas anteriores
mosquito