java - ¿Diferencia entre declarar variables antes o en bucle?




performance loops initialization (21)

Siempre me he preguntado si, en general, declarar una variable desechable antes de un bucle, en lugar de repetidamente dentro del bucle, ¿hace alguna diferencia (rendimiento)? Un ejemplo (bastante inútil) en Java:

a) declaración antes del bucle:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

b) declaración (repetidamente) dentro del bucle:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

¿Cuál es mejor, a o b ?

Sospecho que la declaración de variables repetidas (ejemplo b ) genera más sobrecarga en teoría , pero que los compiladores son lo suficientemente inteligentes como para que no importe. El ejemplo b tiene la ventaja de ser más compacto y limitar el alcance de la variable a donde se usa. Aún así, tiendo a codificar según el ejemplo a .

Edit: Estoy especialmente interesado en el caso de Java.


Answers

Sospecho que algunos compiladores podrían optimizar ambos para ser el mismo código, pero ciertamente no todos. Así que diría que estás mejor con el primero. La única razón para esto último es si quiere asegurarse de que la variable declarada se use solo dentro de su bucle.


Hice una prueba simple:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

vs

for (int i = 0; i < 10; i++) {
    int b = i;
}

Compilé estos códigos con gcc - 5.2.0. Y luego desmonté el main () de estos dos códigos y ese es el resultado:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

vs

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

Cuales son exactamente el mismo resultado. ¿No es una prueba de que los dos códigos producen lo mismo?


Creo que depende del compilador y es difícil dar una respuesta general.


Incluso si sé que mi compilador es lo suficientemente inteligente, no me gustaría confiar en él, y usaré la variante a).

La variante b) tiene sentido para mí solo si necesita desesperadamente hacer que el resultado intermedio no esté disponible después del cuerpo del bucle. Pero no puedo imaginar una situación tan desesperada, de todos modos ...

EDIT: Jon Skeet hizo un muy buen punto, mostrando que la declaración de variables dentro de un bucle puede hacer una diferencia semántica real.


Bueno, siempre se podría hacer un alcance para eso:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

De esta manera solo declaras la variable una vez, y morirá cuando dejes el bucle.


Mi práctica es la siguiente:

  • si el tipo de variable es simple (int, double, ...) prefiero la variante b (adentro).
    Motivo: reduciendo el alcance de la variable.

  • si el tipo de variable no es simple (algún tipo de class o struct ) prefiero la variante a (fuera).
    Motivo: reducir el número de llamadas ctor-dtor.


Depende del idioma y del uso exacto. Por ejemplo, en C # 1 no hizo ninguna diferencia. En C # 2, si la variable local es capturada por un método anónimo (o la expresión lambda en C # 3) puede hacer una diferencia muy significativa.

Ejemplo:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Salida:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

La diferencia es que todas las acciones capturan la misma variable outer , pero cada una tiene su propia variable inner separada.


¿Cuál es mejor, a o b ?

Desde una perspectiva de rendimiento, tendrías que medirlo. (Y en mi opinión, si puedes medir una diferencia, el compilador no es muy bueno).

Desde una perspectiva de mantenimiento, b es mejor. Declare e inicialice las variables en el mismo lugar, en el ámbito más estrecho posible. No deje un hueco entre la declaración y la inicialización, y no contamine los espacios de nombres que no necesita.


Es dependiente del idioma: IIRC C # optimiza esto, por lo que no hay ninguna diferencia, pero JavaScript (por ejemplo) hará la asignación de memoria completa cada vez.


Probé lo mismo en Go, y comparé la salida del compilador usando la go tool compile -S with go 1.9.4

Diferencia cero, según la salida del ensamblador.


Este es un gotcha en VB.NET. El resultado de Visual Basic no reinicializará la variable en este ejemplo:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

Esto imprimirá 0 la primera vez (¡las variables de Visual Basic tienen valores predeterminados cuando se declaran!), Pero cada vez lo hago.

Sin embargo, si agrega a = 0 , obtiene lo que podría esperar:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

A) es una apuesta segura que B) ......... Imagina que estás inicializando la estructura en un bucle en lugar de 'int' o 'float', ¿entonces qué?

me gusta

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

Ciertamente está obligado a enfrentar problemas con fugas de memoria. Por lo tanto, creo que 'A' es una apuesta más segura, mientras que 'B' es vulnerable a la acumulación de memoria, especialmente al trabajar con bibliotecas de código fuente cercanas.


Siempre he pensado que si declara sus variables dentro de su bucle, entonces está perdiendo memoria. Si tienes algo como esto:

for(;;) {
  Object o = new Object();
}

Entonces, no solo es necesario crear el objeto para cada iteración, sino que debe haber una nueva referencia asignada para cada objeto. Parece que si el recolector de basura es lento, entonces tendrá un montón de referencias pendientes que deben limpiarse.

Sin embargo, si tienes esto:

Object o;
for(;;) {
  o = new Object();
}

Entonces solo estás creando una única referencia y asignándole un nuevo objeto cada vez. Claro, puede tardar un poco más en salir del alcance, pero solo hay una referencia pendiente con la que lidiar.


En mi opinión, b es la mejor estructura. En a, el último valor de intermediosResultado se mantiene una vez finalizado el bucle.

Edición: Esto no hace mucha diferencia con los tipos de valor, pero los tipos de referencia pueden ser algo pesados. Personalmente, me gusta que las variables se eliminen de referencia tan pronto como sea posible para la limpieza, yb lo hace por usted,


Es una pregunta interesante. Desde mi experiencia, hay una pregunta fundamental a considerar cuando se debate este asunto por un código:

¿Hay alguna razón por la cual la variable tendría que ser global?

Tiene sentido declarar la variable solo una vez, globalmente, a diferencia de muchas veces localmente, porque es mejor para organizar el código y requiere menos líneas de código. Sin embargo, si solo fuera necesario declararlo localmente dentro de un método, lo inicializaría en ese método para que quede claro que la variable es exclusivamente relevante para ese método. Tenga cuidado de no llamar a esta variable fuera del método en el que se inicializa si elige la última opción; su código no sabrá de lo que está hablando e informará un error.

Además, como nota al margen, no duplique nombres de variables locales entre diferentes métodos, incluso si sus propósitos son casi idénticos; simplemente se vuelve confuso.


Un compañero de trabajo prefiere la primera forma, diciéndole que es una optimización, que prefiere reutilizar una declaración.

Prefiero el segundo (y trato de persuadir a mi compañero de trabajo! ;-)), habiendo leído eso:

  • Reduce el alcance de las variables a donde se necesitan, lo que es bueno.
  • Java optimiza lo suficiente como para no hacer una diferencia significativa en el rendimiento. IIRC, quizás la segunda forma sea aún más rápida.

De todos modos, cae en la categoría de optimización prematura que se basa en la calidad del compilador y / o JVM.


Siempre usaría A (en lugar de confiar en el compilador) y también podría volver a escribir para:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

Esto todavía restringe el resultado intermediateResult al alcance del bucle, pero no se vuelve a declarar durante cada iteración.


Desde una perspectiva de rendimiento, el exterior es (mucho) mejor.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

Ejecuté ambas funciones 1 billón de veces cada una. Fuera () tomó 65 milisegundos. dentro () tomó 1.5 segundos.


Probé JS con Node 4.0.0 si alguien está interesado. La declaración fuera del bucle dio como resultado una mejora del rendimiento de ~ .5 ms en promedio en más de 1000 intentos con 100 millones de iteraciones de bucle por ensayo. Así que diré, adelante, y escríbalo de la manera más legible / mantenible que es B, imo. Yo pondría mi código en un violín, pero usé el módulo Nodo de rendimiento ahora. Aquí está el código:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)

Hay una diferencia en C # si está utilizando la variable en un lambda, etc. Pero en general el compilador básicamente hará lo mismo, asumiendo que la variable solo se usa dentro del bucle.

Dado que son básicamente los mismos: tenga en cuenta que la versión b hace que sea mucho más obvio para los lectores que la variable no es, y no puede, usarse después del bucle. Además, la versión b es mucho más fácil de refaccionar. Es más difícil extraer el cuerpo del bucle en su propio método en la versión a. Además, la versión b le asegura que no hay ningún efecto secundario en dicha refactorización.

Por lo tanto, la versión a me molesta hasta el final, porque no tiene ningún beneficio y hace que sea mucho más difícil razonar sobre el código ...


tal vez con una funcion?

public void doSomething(List<Type> types, List<Type> types2){
  for(Type t1 : types){
    for (Type t : types2) {
      if (some condition) {
         //do something and return...
         return;
      }
    }
  }
}




java performance loops variables initialization