while programas for flujo ejemplos diagrama ciclos ciclo bucle java optimization while-loop

for - programas en java de do while



Declarar variables dentro o fuera de un bucle (20)

¿Por qué funciona bien lo siguiente?

String str; while (condition) { str = calculateStr(); ..... }

Pero se dice que este es peligroso / incorrecto:

while (condition) { String str = calculateStr(); ..... }

¿Es necesario declarar variables fuera del bucle?


El alcance de las variables locales siempre debe ser lo más pequeño posible.

En su ejemplo, supongo que no se usa str fuera del ciclo while, de lo contrario no estaría haciendo la pregunta, ya que declararlo dentro del ciclo while no sería una opción, ya que no se compilaría.

Por lo tanto, dado que str no se usa fuera del bucle, el alcance más pequeño posible para str está dentro del bucle while.

Entonces, la respuesta es enfáticamente que str debe ser declarado dentro del bucle while. No ifs, no ands, no buts.

El único caso en el que se podría infringir esta regla es si, por algún motivo, es de vital importancia que cada ciclo de reloj se elimine del código, en cuyo caso puede considerar crear una instancia de algo en un ámbito externo y reutilizarlo. re-instanciarlo en cada iteración de un ámbito interno. Sin embargo, esto no se aplica a su ejemplo, debido a la inmutabilidad de las cadenas en java: siempre se creará una nueva instancia de str al principio de su bucle y tendrá que desecharse al final, así que No hay posibilidad de optimizar allí.

EDITAR: (inyectando mi comentario abajo en la respuesta)

En cualquier caso, la forma correcta de hacer las cosas es escribir todo el código correctamente, establecer un requisito de rendimiento para su producto, medir su producto final en relación con este requisito, y si no lo satisface, entonces optimice las cosas. Y lo que generalmente sucede es que encuentra formas de proporcionar optimizaciones algorítmicas agradables y formales en solo un par de lugares que hacen que nuestro programa cumpla con sus requisitos de rendimiento en lugar de tener que recorrer toda su base de código y modificar y piratear las cosas. Para apretar ciclos de reloj aquí y allá.


Advertencia para casi todos en esta pregunta: Aquí hay un código de ejemplo donde dentro del bucle puede ser 200 veces más lento en mi computadora con Java 7 (y el consumo de memoria también es ligeramente diferente) Pero se trata de asignación y no solo de alcance.

public class Test { private final static int STUFF_SIZE = 512; private final static long LOOP = 10000000l; private static class Foo { private long[] bigStuff = new long[STUFF_SIZE]; public Foo(long value) { setValue(value); } public void setValue(long value) { // Putting value in a random place. bigStuff[(int) (value % STUFF_SIZE)] = value; } public long getValue() { // Retrieving whatever value. return bigStuff[STUFF_SIZE / 2]; } } public static long test1() { long total = 0; for (long i = 0; i < LOOP; i++) { Foo foo = new Foo(i); total += foo.getValue(); } return total; } public static long test2() { long total = 0; Foo foo = new Foo(0); for (long i = 0; i < LOOP; i++) { foo.setValue(i); total += foo.getValue(); } return total; } public static void main(String[] args) { long start; start = System.currentTimeMillis(); test1(); System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); test2(); System.out.println(System.currentTimeMillis() - start); } }

Conclusión: Dependiendo del tamaño de la variable local, la diferencia puede ser enorme, incluso con variables no tan grandes.

Solo para decir que a veces, fuera o dentro del bucle SI importa.


Como muchas personas han señalado,

String str; while(condition){ str = calculateStr(); ..... }

NO es mejor que esto:

while(condition){ String str = calculateStr(); ..... }

Así que no declare variables fuera de sus ámbitos si no lo está reutilizando ...


Comparé el código de bytes de esos dos ejemplos (similares):

Veamos el ejemplo 1 .

package inside; public class Test { public static void main(String[] args) { while(true){ String str = String.valueOf(System.currentTimeMillis()); System.out.println(str); } } }

después de javac Test.java , javap -c Test obtendrás:

public class inside.Test extends java.lang.Object{ public inside.Test(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J 3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String; 6: astore_1 7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 10: aload_1 11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: goto 0 }

Veamos el ejemplo 2 .

package outside; public class Test { public static void main(String[] args) { String str; while(true){ str = String.valueOf(System.currentTimeMillis()); System.out.println(str); } } }

después de javac Test.java , javap -c Test obtendrás:

public class outside.Test extends java.lang.Object{ public outside.Test(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J 3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String; 6: astore_1 7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 10: aload_1 11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: goto 0 }

Las observaciones muestran que no hay diferencia entre esos dos ejemplos. Es el resultado de las especificaciones JVM ...

Pero en el nombre de la mejor práctica de codificación, se recomienda declarar la variable en el ámbito más pequeño posible (en este ejemplo está dentro del bucle, ya que este es el único lugar donde se usa la variable).


Creo que el mejor recurso para responder tu pregunta sería la siguiente publicación:

¿Diferencia entre declarar variables antes o en bucle?

Según mi entendimiento, esto dependería del lenguaje. IIRC Java optimiza esto, por lo que no hay ninguna diferencia, pero JavaScript (por ejemplo) hará toda la asignación de memoria cada vez en el bucle. En Java, particularmente, creo que el segundo se ejecutará más rápido cuando se realice el perfilado.


Creo que el tamaño del objeto también importa. En uno de mis proyectos, habíamos declarado e inicializado una gran matriz bidimensional que estaba haciendo que la aplicación lanzara una excepción de memoria insuficiente. Movimos la declaración fuera del bucle y borramos la matriz al comienzo de cada iteración.


De acuerdo con la guía de desarrollo de Google Android, el alcance variable debería ser limitado. Por favor revise este enlace:

Limitar el alcance de la variable


Declarar dentro del bucle limita el alcance de la variable respectiva. Todo depende de los requisitos del proyecto sobre el alcance de la variable.


Declarar objetos en el alcance más pequeño mejora la legibilidad .

El rendimiento no importa para los compiladores de hoy (en este escenario)
Desde una perspectiva de mantenimiento, la opción es mejor.
Declare e inicialice las variables en el mismo lugar, en el ámbito más estrecho posible.

Como Donald Ervin Knuth dijo:

"Deberíamos olvidarnos de las pequeñas eficiencias, digamos que aproximadamente el 97% del tiempo: la optimización prematura es la raíz de todo mal"

es decir, una situación en la que un programador permite que las consideraciones de rendimiento afecten el diseño de un fragmento de código. Esto puede dar como resultado un diseño que no sea tan limpio como podría haber sido o un código incorrecto, ya que el código se complica con la optimización y el programador se distrae con la optimización .


En el interior, cuanto menor es el alcance de la variable, mejor se ve


En verdad, la pregunta formulada anteriormente es un problema de programación. ¿Cómo te gustaría programar tu código? ¿Dónde necesita el ''STR'' para acceder? No se utiliza declarar una variable que se usa localmente como una variable global. Fundamentos de la programación que creo.


Estos dos ejemplos dan como resultado lo mismo. Sin embargo, el primero le proporciona el uso de la variable str fuera del bucle while; el segundo no es


La declaración de String str fuera del bucle wile permite que se haga referencia dentro y fuera del bucle while. Al declarar String str dentro del bucle while solo se puede hacer referencia dentro del bucle while.


La variable str estará disponible y reservará algo de espacio en la memoria incluso después de ejecutarse debajo del código.

String str; while(condition){ str = calculateStr(); ..... }

La variable str no estará disponible y también se liberará la memoria que se asignó para la variable str en el código siguiente.

while(condition){ String str = calculateStr(); ..... }

Si seguimos la segunda, seguramente esto reducirá la memoria de nuestro sistema y aumentará el rendimiento.



Si no necesita usar el str después del bucle while (relacionado con el alcance), entonces la segunda condición, es decir

while(condition){ String str = calculateStr(); ..... }

es mejor ya que si define un objeto en la pila solo si la condition es verdadera. Lo uso si lo necesitas.


Una solución a este problema podría ser proporcionar un alcance variable que encapsule el bucle while:

{ // all tmp loop variables here .... // .... String str; while(condition){ str = calculateStr(); ..... } }

Se desvincularán automáticamente cuando finalice el alcance externo.


Usted tiene un riesgo de NullPointerException si su método NullPointerException calculateStr() devuelve un valor nulo y luego intenta llamar a un método en str.

Más generalmente, evite tener variables con un valor nulo . Es más fuerte para los atributos de clase, por cierto.


si quieres usar str fuera de looop también; declararlo afuera. De lo contrario, la segunda versión está bien.


Por favor pase a la respuesta actualizada ...

Para aquellos que se preocupan por el rendimiento, saque el System.out y limite el ciclo a 1 byte. Utilizando el doble (prueba 1/2) y el uso de String (3/4), los tiempos transcurridos en milisegundos se indican a continuación con Windows 7 Professional 64 bit y JDK-1.7.0_21. Los códigos de bytes (también se dan a continuación para test1 y test2) no son los mismos. Era demasiado perezoso para probar con objetos mutables y relativamente complejos.

doble

Test1 tomó: 2710 msecs

Test2 tomó: 2790 msegs.

Cadena (solo reemplaza el doble por una cuerda en las pruebas)

Test3 tomó: 1200 msecs

Test4 tomó: 3000 msecs

Compilando y obteniendo bytecode

javac.exe LocalTest1.java javap.exe -c LocalTest1 > LocalTest1.bc public class LocalTest1 { public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); double test; for (double i = 0; i < 1000000000; i++) { test = i; } long finish = System.currentTimeMillis(); System.out.println("Test1 Took: " + (finish - start) + " msecs"); } } public class LocalTest2 { public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); for (double i = 0; i < 1000000000; i++) { double test = i; } long finish = System.currentTimeMillis(); System.out.println("Test1 Took: " + (finish - start) + " msecs"); } } Compiled from "LocalTest1.java" public class LocalTest1 { public LocalTest1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 3: lstore_1 4: dconst_0 5: dstore 5 7: dload 5 9: ldc2_w #3 // double 1.0E9d 12: dcmpg 13: ifge 28 16: dload 5 18: dstore_3 19: dload 5 21: dconst_1 22: dadd 23: dstore 5 25: goto 7 28: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 31: lstore 5 33: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 36: new #6 // class java/lang/StringBuilder 39: dup 40: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 43: ldc #8 // String Test1 Took: 45: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 48: lload 5 50: lload_1 51: lsub 52: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 55: ldc #11 // String msecs 57: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 60: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 63: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: return } Compiled from "LocalTest2.java" public class LocalTest2 { public LocalTest2(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 3: lstore_1 4: dconst_0 5: dstore_3 6: dload_3 7: ldc2_w #3 // double 1.0E9d 10: dcmpg 11: ifge 24 14: dload_3 15: dstore 5 17: dload_3 18: dconst_1 19: dadd 20: dstore_3 21: goto 6 24: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 27: lstore_3 28: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 31: new #6 // class java/lang/StringBuilder 34: dup 35: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 38: ldc #8 // String Test1 Took: 40: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 43: lload_3 44: lload_1 45: lsub 46: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 49: ldc #11 // String msecs 51: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 54: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 57: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 60: return }

RESPUESTA ACTUALIZADA

Realmente no es fácil comparar el rendimiento con todas las optimizaciones de JVM. Sin embargo, es algo posible. Mejor test y resultados detallados en Google Caliper.

  1. Algunos detalles en el blog: ¿Debería declarar una variable dentro de un bucle o antes del bucle?
  2. Repositorio de GitHub: https://github.com/gunduru/jvdt
  3. Resultados de la prueba para doble caso y bucle 100M (y sí, todos los detalles de JVM): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4
  • Declarado Antes 1,759.209 ns
  • DeclaredInside 2,242.308 ns

Código de prueba parcial para doble declaración

Esto no es idéntico al código anterior. Si solo codificas un bucle ficticio, JVM lo omite, así que al menos debes asignar y devolver algo. Esto también se recomienda en la documentación de Caliper.

@Param int size; // Set automatically by framework, provided in the Main /** * Variable is declared inside the loop. * * @param reps * @return */ public double timeDeclaredInside(int reps) { /* Dummy variable needed to workaround smart JVM */ double dummy = 0; /* Test loop */ for (double i = 0; i <= size; i++) { /* Declaration and assignment */ double test = i; /* Dummy assignment to fake JVM */ if(i == size) { dummy = test; } } return dummy; } /** * Variable is declared before the loop. * * @param reps * @return */ public double timeDeclaredBefore(int reps) { /* Dummy variable needed to workaround smart JVM */ double dummy = 0; /* Actual test variable */ double test = 0; /* Test loop */ for (double i = 0; i <= size; i++) { /* Assignment */ test = i; /* Not actually needed here, but we need consistent performance results */ if(i == size) { dummy = test; } } return dummy; }

Resumen: declarado Antes de un mejor rendimiento, realmente pequeño, y va en contra del principio de alcance más pequeño. JVM debería hacer esto por ti