Respuestas:
En su ejemplo, supongo str
que no se usa fuera del while
bucle, de lo contrario no haría la pregunta, porque declararlo dentro del while
bucle no sería una opción, ya que no se compilaría.
Entonces, dado str
que no se usa fuera del ciclo, el alcance más pequeño posible str
está dentro del ciclo while.
Entonces, la respuesta es enfáticamente que str
absolutamente debe declararse dentro del ciclo while. Sin peros, sin peros, sin peros.
El único caso en el que podría violarse esta regla es si, por alguna razón, es de vital importancia que cada ciclo de reloj se elimine del código, en cuyo caso es posible que desee considerar crear una instancia de algo en un ámbito externo y reutilizarlo en lugar de volver a crear una instancia 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 comienzo de su ciclo y tendrá que desecharse al final de la misma, por lo que No hay posibilidad de optimizar allí.
EDITAR: (inyectando mi comentario a continuación en la respuesta)
En cualquier caso, la forma correcta de hacer las cosas es escribir todo su código correctamente, establecer un requisito de rendimiento para su producto, medir su producto final en función de este requisito y, si no lo cumple, ir a optimizar las cosas. Y lo que generalmente sucede es que encuentra formas de proporcionar algunas 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 revisar toda su base de código y modificar y piratear cosas en para apretar los ciclos de reloj aquí y allá.
Comparé el código de bytes de esos dos ejemplos (similares):
Veamos 1. ejemplo :
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 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 2. ejemplo :
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 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 nombre de la mejor práctica de codificación, se recomienda declarar la variable en el alcance más pequeño posible (en este ejemplo, está dentro del bucle, ya que este es el único lugar donde se usa la variable).
final
amantes: declarar str
como final
en el inside
caso del paquete tampoco hace diferencia =)
Declarar objetos en el ámbito 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 segunda opción es mejor.
Declare e inicialice las variables en el mismo lugar, en el ámbito más estrecho posible.
Como dijo Donald Ervin Knuth :
"Deberíamos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal"
es decir, la situación en la que un programador deja 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 es tan limpio como podría haber sido o un código incorrecto, porque la optimización complica el código y el optimizador distrae al programador .
Pase a la respuesta actualizada ...
Para aquellos que se preocupan por el rendimiento, saque System.out y limite el ciclo a 1 byte. Usando doble (prueba 1/2) y usando Cadena (3/4), los tiempos transcurridos en milisegundos se muestran a continuación con Windows 7 Professional de 64 bits y JDK-1.7.0_21. Los códigos de bytes (que también se proporcionan a continuación para test1 y test2) no son lo mismo. Era demasiado vago para probar con objetos mutables y relativamente complejos.
doble
Prueba1 tomó: 2710 ms
Test2 tomó: 2790 ms
Cadena (solo reemplace doble con cadena en las pruebas)
Test3 tomó: 1200 ms
Test4 tomó: 3000 ms
Compilar y obtener 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
}
Realmente no es fácil comparar el rendimiento con todas las optimizaciones de JVM. Sin embargo, es algo posible. Mejor prueba y resultados detallados en Google Caliper
Esto no es idéntico al código anterior. Si solo codifica un bucle ficticio, JVM lo omite, por lo que al menos debe 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 indica 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
Una solución a este problema podría ser proporcionar un alcance variable que encapsule el ciclo while:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
Serían automáticamente desreferenciados cuando finalice el alcance externo.
Si no necesita usar el str
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 condition
es verdadero. Es decir, úsalo si lo necesitas
Creo que el mejor recurso para responder a su pregunta sería la siguiente publicación:
¿Diferencia entre declarar variables antes o en bucle?
Según tengo entendido, esto dependería del idioma. 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 termine el perfil.
Como mucha gente ha señalado,
String str;
while(condition){
str = calculateStr();
.....
}
NO es mejor que esto:
while(condition){
String str = calculateStr();
.....
}
Por lo tanto, no declare variables fuera de su alcance si no lo está reutilizando ...
Declarar String str fuera del bucle wile permite que se haga referencia dentro y fuera del bucle while. Declarar String str dentro del ciclo while permite que solo se haga referencia dentro del ciclo while.
Las variables deben declararse lo más cerca posible del lugar donde se usan.
Facilita la RAII (Adquisición de recursos es inicialización) .
Mantiene el alcance de la variable ajustado. Esto permite que el optimizador funcione mejor.
Según la guía de desarrollo de Google Android, el alcance variable debe ser limitado. Por favor revise este enlace:
La str
variable 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 str
variable no estará disponible y también se liberará la memoria que se asignó para la str
variable en el siguiente código.
while(condition){
String str = calculateStr();
.....
}
Si seguimos el segundo seguramente esto reducirá la memoria de nuestro sistema y aumentará el rendimiento.
En verdad, la pregunta mencionada anteriormente es un problema de programación. ¿Cómo le gustaría programar su código? ¿Dónde necesita el 'STR' para acceder? No tiene sentido declarar una variable que se usa localmente como una variable global. Conceptos básicos de programación, creo.
Advertencia para casi todos en esta pregunta: Aquí hay un código de muestra en el que dentro del bucle puede ser fácilmente 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 la asignación y no solo del 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 circuito sí importa.
bigStuff[(int) (value % STUFF_SIZE)] = value;
(Pruebe un valor de 2147483649L)
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 falta de memoria. En su lugar, retiramos la declaración del ciclo y borramos la matriz al comienzo de cada iteración.
Corre el riesgo de NullPointerException
que su calculateStr()
método devuelva nulo y luego intente llamar a un método en str.
De manera más general, evite tener variables con un valor nulo . Es más fuerte para los atributos de clase, por cierto.
NullPointerException.
Si este código intentara return str;
encontrar un error de compilación.