Differenza tra la dichiarazione di variabili prima o in loop?


312

Mi sono sempre chiesto se, in generale, dichiarare una variabile usa e getta prima di un loop, anziché ripetutamente all'interno del loop, faccia qualche differenza (di performance)? Un esempio (abbastanza inutile) in Java:

a) dichiarazione prima del ciclo:

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

b) dichiarazione (ripetutamente) all'interno del circuito:

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

Quale è meglio, a o b ?

Ho il sospetto che la dichiarazione di variabili ripetute (esempio b ) crei un maggior sovraccarico in teoria , ma che i compilatori siano abbastanza intelligenti da non importare. L'esempio b ha il vantaggio di essere più compatto e di limitare l'ambito della variabile a dove viene usata. Tendo comunque a codificare secondo l'esempio a .

Modifica: sono particolarmente interessato al caso Java.


Questo è importante quando si scrive codice Java per la piattaforma Android. Google suggerisce che per il codice time-critical di dichiarare variabili di incremento al di fuori di un ciclo for, come se all'interno del ciclo for, lo dichiari nuovamente ogni volta in quell'ambiente. La differenza di prestazioni è molto evidente per algoritmi costosi.
AaronCarson il

1
@AaronCarson potresti fornire link a questo suggerimento di Google
Vitaly Zinchenko

Risposte:


256

Quale è meglio, a o b ?

Dal punto di vista delle prestazioni, dovresti misurarlo. (E secondo me, se puoi misurare una differenza, il compilatore non è molto buono).

Dal punto di vista della manutenzione, b è migliore. Dichiarare e inizializzare le variabili nello stesso posto, nell'ambito più ristretto possibile. Non lasciare un buco tra la dichiarazione e l'inizializzazione e non inquinare gli spazi dei nomi che non è necessario.


5
Invece di Double, se si tratta di String, il caso "b" è ancora migliore?
Antoops,

3
@Antoops: sì, b è meglio per motivi che non hanno nulla a che fare con il tipo di dati della variabile dichiarata. Perché sarebbe diverso per le stringhe?
Daniel Earwicker,

215

Bene, ho eseguito i tuoi esempi A e B 20 volte ciascuno, ripetendo 100 milioni di volte. (JVM - 1.5.0)

A: tempo medio di esecuzione: 0,074 sec

B: tempo medio di esecuzione: 0,067 sec

Con mia sorpresa, B era leggermente più veloce. Per quanto i computer siano ormai difficili, è difficile dire se è possibile misurarli con precisione. Lo codificherei anche nel modo A, ma direi che non importa.


12
Mi hai battuto Stavo per pubblicare i miei risultati per la profilazione, ho ottenuto più o meno lo stesso e sì sorprendentemente B è più veloce davvero avrei pensato A se avessi avuto bisogno di scommettere su di esso.
Mark Davidson,

14
Non c'è molta sorpresa: quando la variabile è locale nel ciclo, non è necessario conservarla dopo ogni iterazione, quindi può rimanere in un registro.

142
+1 per averlo effettivamente testato , non solo un'opinione / teoria che l'OP avrebbe potuto inventare da solo.
MGOwen,

3
@GoodPerson a dire il vero, mi piacerebbe che fosse fatto. Ho eseguito questo test circa 10 volte sulla mia macchina per 50.000.000-100.000.000 di iterazioni con quasi un codice identico (che mi piacerebbe condividere con chiunque desideri eseguire le statistiche). Le risposte sono state divise quasi allo stesso modo in entrambi i casi di solito da un margine di 900 ms (oltre 50 milioni di iterazioni) che non è molto. Anche se il mio primo pensiero è che sarà "rumore", potrebbe inclinarsi di un po '. Questo sforzo mi sembra puramente accademico (per la maggior parte delle applicazioni della vita reale). Mi piacerebbe comunque vedere un risultato;) Qualcuno è d'accordo?
javatarz,

3
La visualizzazione dei risultati dei test senza documentare l'installazione è inutile. Ciò è particolarmente vero in questo caso, in cui entrambi i frammenti di codice producono un bytecode identico, quindi qualsiasi differenza misurata è solo un segno di condizioni di test insufficienti.
Holger,

66

Dipende dalla lingua e dall'uso esatto. Ad esempio, in C # 1 non ha fatto differenza. In C # 2, se la variabile locale viene catturata da un metodo anonimo (o espressione lambda in C # 3), può fare una differenza molto significativa.

Esempio:

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();
        }
    }
}

Produzione:

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 differenza è che tutte le azioni acquisiscono la stessa outervariabile, ma ognuna ha una propria innervariabile separata .


3
nell'esempio B (domanda originale), crea effettivamente una nuova variabile ogni volta? cosa sta succedendo agli occhi della pila?
Royi Namir,

@Jon, era un bug in C # 1.0? Idealmente non dovrebbe Outeressere 9?
nawfal,

@nawfal: non so cosa intendi. Le espressioni lambda non erano in 1.0 ... e Outer è 9. Che bug intendi?
Jon Skeet,

@nawfal: il mio punto è che in C # 1.0 non c'erano funzioni linguistiche in cui si potesse distinguere tra dichiarare una variabile all'interno di un ciclo e dichiararla all'esterno (supponendo che entrambi fossero compilati). Ciò è cambiato in C # 2.0. Nessun bug.
Jon Skeet,

@JonSkeet Oh sì, ti capisco ora, ho completamente ignorato il fatto che non puoi chiudere variabili come questa in 1.0, mio ​​male! :)
nawfal,

35

Quello che segue è ciò che ho scritto e compilato in .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

Questo è ciò che ottengo da .NET Reflector quando CIL viene restituito nel codice.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Quindi entrambi sembrano esattamente uguali dopo la compilazione. Nelle lingue gestite il codice viene convertito in codice CL / byte e al momento dell'esecuzione viene convertito in linguaggio macchina. Quindi, in linguaggio macchina, un doppio non può nemmeno essere creato nello stack. Potrebbe essere solo un registro poiché il codice riflette che si tratta di una variabile temporanea per la WriteLinefunzione. Esistono regole di ottimizzazione di un intero set solo per i loop. Quindi il ragazzo medio non dovrebbe preoccuparsene, specialmente nelle lingue gestite. Ci sono casi in cui puoi ottimizzare la gestione del codice, ad esempio, se devi concatenare un gran numero di stringhe usando solo string a; a+=anotherstring[i]vs usandoStringBuilder. C'è una grande differenza nelle prestazioni tra i due. Ci sono molti casi in cui il compilatore non può ottimizzare il codice, perché non riesce a capire cosa si intende in un ambito più ampio. Ma può praticamente ottimizzare le cose di base per te.


int j = 0 per (; j <0x3e8; j ++) in questo modo dichiarato una volta entrambe le variabili e non ognuna per ciclo. 2) il compito è più grasso di tutte le altre opzioni. 3) Quindi la regola di bestpractice è qualsiasi dichiarazione al di fuori dell'iterazione per.
luka,

24

Questo è un gotcha in VB.NET. Il risultato di Visual Basic non reinizializza la variabile in questo esempio:

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

' Output: 0 1 2 3 4...

Questo stamperà 0 la prima volta (le variabili di Visual Basic hanno valori predefiniti quando dichiarate!) Ma iogni volta dopo.

Se aggiungi un = 0, però, ottieni quello che potresti aspettarti:

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

'Output: 0 0 0 0 0...

1
Uso VB.NET da anni e non mi ero mai imbattuto in questo !!
ChrisA

12
Sì, è spiacevole capirlo in pratica.
Michael Haren,

Ecco un riferimento a riguardo di Paul Vick: panopticoncentral.net/archive/2006/03/28/11552.aspx
ferventcoder

1
@eschneider @ferventcoder Purtroppo @PaulV ha deciso di abbandonare i suoi vecchi post sul blog , quindi questo è ora un link non funzionante.
Mark Hurd,

sì, proprio di recente ho incontrato questo; cercavo alcuni documenti ufficiali su questo ...
Eric Schneider,

15

Ho fatto un semplice test:

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

vs

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

Ho compilato questi codici con gcc - 5.2.0. E poi ho smontato il main () di questi due codici e questo è il risultato:

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 

Che sono esattamente lo stesso risultato. non è una prova che i due codici producano la stessa cosa?


3
sì, ed è bello che tu l'abbia fatto, ma questo ritorna a ciò che la gente diceva sulla dipendenza dal linguaggio / compilatore. Mi chiedo come sarebbe influenzato JIT o interpretazione del linguaggio interpretato.
user137717

12

Dipende dal linguaggio - IIRC C # lo ottimizza, quindi non c'è alcuna differenza, ma JavaScript (ad esempio) eseguirà l'intera allocazione della memoria ogni volta.


Sì, ma ciò non equivale a molto. Ho eseguito un semplice test con un ciclo for eseguito 100 milioni di volte e ho scoperto che la differenza più grande a favore della dichiarazione al di fuori del loop era di 8 ms. Di solito era più o meno 3-4 e talvolta dichiarava fuori dal ciclo WORSE (fino a 4 ms), ma non era tipico.
user137717

11

Userei sempre A (piuttosto che fare affidamento sul compilatore) e potrei anche riscrivere a:

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

Ciò si limita ancora intermediateResultall'ambito del ciclo, ma non viene dichiarato nuovamente durante ogni iterazione.


12
Volete concettualmente che la variabile viva per la durata del ciclo anziché separatamente per iterazione? Lo faccio raramente. Scrivi il codice che rivela la tua intenzione il più chiaramente possibile, a meno che tu non abbia una ragione molto, molto buona per fare diversamente.
Jon Skeet,

4
Ah, bel compromesso, non ci avevo mai pensato! IMO, il codice diventa un po 'meno visivamente' chiaro 'però)
Rabarberski

2
@Jon - Non ho idea di cosa stia effettivamente facendo l'OP con il valore intermedio. Ho pensato che fosse un'opzione che vale la pena considerare.
Trittico

6

Secondo me, b è la struttura migliore. In a, l'ultimo valore di intermedioRisult rimane attivo al termine del ciclo.

Modifica: questo non fa molta differenza con i tipi di valore, ma i tipi di riferimento possono essere piuttosto pesanti. Personalmente, mi piace che le variabili vengano dereferenziate il prima possibile per la pulizia, e b lo fa per te,


sticks around after your loop is finished- anche se questo non ha importanza in un linguaggio come Python, dove i nomi associati restano attivi fino alla fine della funzione.
nuovo123456

@ new123456: L'OP ha richiesto specifiche di Java, anche se la domanda è stata posta in modo piuttosto generico. Molti linguaggi derivati ​​da C hanno ambito a livello di blocco: C, C ++, Perl (con la myparola chiave), C # e Java per nominare 5 che ho usato.
Powerlord,

Lo so - è stata un'osservazione, non una critica.
nuovo123456,

5

Ho il sospetto che alcuni compilatori potrebbero ottimizzare entrambi per essere lo stesso codice, ma certamente non tutti. Quindi direi che stai meglio con il primo. L'unico motivo di quest'ultimo è se vuoi assicurarti che la variabile dichiarata sia usata solo all'interno del tuo ciclo.


5

Come regola generale, dichiaro le mie variabili nell'ambito più interno possibile. Quindi, se non stai usando intermedioResult al di fuori del ciclo, allora andrei con B.


5

Un collega preferisce il primo modulo, dicendo che è un'ottimizzazione, preferendo riutilizzare una dichiarazione.

Preferisco il secondo (e provo a convincere il mio collega! ;-)), dopo aver letto che:

  • Riduce la portata delle variabili nel punto in cui sono necessarie, il che è positivo.
  • Java ottimizza abbastanza per non fare alcuna differenza significativa nelle prestazioni. IIRC, forse la seconda forma è ancora più veloce.

Ad ogni modo, rientra nella categoria dell'ottimizzazione prematura che si basa sulla qualità del compilatore e / o JVM.


5

C'è una differenza in C # se stai usando la variabile in una lambda, ecc. Ma in generale il compilatore farà sostanzialmente la stessa cosa, supponendo che la variabile sia usata solo all'interno del ciclo.

Dato che sono sostanzialmente gli stessi: nota che la versione b rende molto più ovvio ai lettori che la variabile non è, e non può, essere usata dopo il ciclo. Inoltre, la versione b è molto più facilmente riformulata. È più difficile estrarre il corpo del loop nel suo metodo nella versione a. Inoltre, la versione b ti assicura che non esiste alcun effetto collaterale per un tale refactoring.

Quindi, la versione a mi infastidisce senza fine, perché non ha alcun vantaggio e rende molto più difficile ragionare sul codice ...


5

Bene, potresti sempre fare uno scopo per quello:

{ //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);
    }
}

In questo modo dichiari la variabile una sola volta e morirà quando lasci il ciclo.


4

Ho sempre pensato che se dichiari le tue variabili all'interno del tuo ciclo, stai sprecando memoria. Se hai qualcosa del genere:

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

Quindi non solo l'oggetto deve essere creato per ogni iterazione, ma deve essere assegnato un nuovo riferimento per ogni oggetto. Sembra che se il garbage collector è lento, avrai un sacco di riferimenti penzolanti che devono essere ripuliti.

Tuttavia, se hai questo:

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

Quindi stai solo creando un singolo riferimento e assegnandogli un nuovo oggetto ogni volta. Certo, potrebbe volerci un po 'più tempo per uscire dal campo di applicazione, ma poi c'è solo un riferimento penzolante da affrontare.


3
Un nuovo riferimento non viene allocato per ciascun oggetto, anche se il riferimento è dichiarato all'interno del ciclo 'for'. In ENTRAMBI i casi: 1) 'o' è una variabile locale e lo spazio di stack viene allocato una volta per essa all'inizio della funzione. 2) C'è un nuovo oggetto creato in ogni iterazione. Quindi non c'è differenza nelle prestazioni. Per l'organizzazione del codice, la leggibilità e la manutenibilità, è meglio dichiarare il riferimento all'interno del ciclo.
Goditi Bhatia il

1
Anche se non posso parlare per Java, in .NET il riferimento non è "allocato" per ogni oggetto nel primo esempio. C'è una singola voce nello stack per quella variabile locale (al metodo). Per i tuoi esempi, l'IL creato è identico.
Jesse C. Slicer,

3

Penso che dipenda dal compilatore ed è difficile dare una risposta generale.


3

La mia pratica è la seguente:

  • se il tipo di variabile è semplice (int, double, ...) preferisco la variante b (interna).
    Motivo: riduzione dell'ambito della variabile.

  • se il tipo di variabile non è semplice (qualche tipo di classo struct) preferisco la variante a (esterna).
    Motivo: riduzione del numero di chiamate ctor-dtor.


1

Dal punto di vista delle prestazioni, l'esterno è (molto) migliore.

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;
    }
}

Ho eseguito entrambe le funzioni 1 miliardo di volte ciascuna. outside () ha impiegato 65 millisecondi. inside () ha impiegato 1,5 secondi.


2
Deve essere stata quindi una compilation non ottimizzata per il debug, eh?
Tomasz Przychodzki,

int j = 0 per (; j <0x3e8; j ++) in questo modo dichiarato una volta entrambe le variabili e non ognuna per ciclo. 2) il compito è più grasso di tutte le altre opzioni. 3) Quindi la regola di bestpractice è qualsiasi dichiarazione al di fuori dell'iterazione per.
luka,

1

Ho testato per JS con Node 4.0.0 se qualcuno è interessato. La dichiarazione al di fuori del ciclo ha comportato un miglioramento delle prestazioni di ~ 0,5 ms in media oltre 1000 prove con 100 milioni di iterazioni di ciclo per prova. Quindi dirò di andare avanti e scriverlo nel modo più leggibile / gestibile che è B, imo. Vorrei mettere il mio codice in un violino, ma ho usato il modulo Node di performance-now. Ecco il codice:

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)

0

A) è una scommessa sicura di B) ......... Immagina se stai inizializzando la struttura in loop piuttosto che "int" o "float", e allora?

piace

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?
}

Siete sicuramente tenuti ad affrontare problemi con perdite di memoria !. Quindi credo che 'A' sia una scommessa più sicura mentre 'B' è vulnerabile all'accumulo di memoria, specialmente se si lavora con librerie di sorgenti vicine.


0

È una domanda interessante Dalla mia esperienza c'è un'ultima domanda da considerare quando si discute di questo argomento per un codice:

C'è qualche motivo per cui la variabile dovrebbe essere globale?

Ha senso dichiarare la variabile una sola volta, a livello globale, anziché molte volte a livello locale, perché è meglio per organizzare il codice e richiede meno righe di codice. Tuttavia, se deve essere dichiarato solo localmente all'interno di un metodo, lo inizializzerei in quel metodo, quindi è chiaro che la variabile è pertinente esclusivamente a quel metodo. Fai attenzione a non chiamare questa variabile al di fuori del metodo in cui è inizializzata se scegli quest'ultima opzione: il tuo codice non saprà di cosa stai parlando e segnalerà un errore.

Inoltre, come nota a margine, non duplicare i nomi delle variabili locali tra metodi diversi anche se i loro scopi sono quasi identici; diventa confuso.


1
lol Non sono d'accordo per così tanti motivi ... Tuttavia, nessun voto negativo ... Rispetto il tuo diritto di scegliere
Grantly

0

questa è la forma migliore

double intermediateResult;
int i = byte.MinValue;

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

1) in questo modo dichiarato una volta entrambe le variabili, e non ognuna per ciclo. 2) il compito è più grasso di tutte le altre opzioni. 3) Quindi la regola di bestpractice è qualsiasi dichiarazione al di fuori dell'iterazione per.


0

Ho provato la stessa cosa in Go e confrontato l'output del compilatore usando go tool compile -Scon go 1.9.4

Differenza zero, secondo l'uscita dell'assemblatore.


0

Ho avuto questa stessa domanda per molto tempo. Quindi ho testato un codice ancora più semplice.

Conclusione: per tali casi non vi è alcuna differenza di prestazioni.

Custodia per anello esterno

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

Custodia interna ad anello

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

Ho controllato il file compilato sul decompilatore di IntelliJ e in entrambi i casi ho ottenuto lo stesso Test.class

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

Ho anche smontato il codice per entrambi i casi usando il metodo indicato in questo risposta . Mostrerò solo le parti pertinenti alla risposta

Custodia per anello esterno

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Custodia interna ad anello

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Se presti molta attenzione, solo i Slotassegnati a ieintermediateResult in LocalVariableTablevengono scambiate come prodotto del loro ordine di apparizione. La stessa differenza nello slot si riflette in altre righe di codice.

  • Non viene eseguita alcuna operazione aggiuntiva
  • intermediateResult è ancora una variabile locale in entrambi i casi, quindi non vi è alcuna differenza nel tempo di accesso.

BONUS

I compilatori fanno un sacco di ottimizzazione, dai un'occhiata a cosa succede in questo caso.

Caso di lavoro zero

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

Zero lavori decompilati

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

-1

Anche se so che il mio compilatore è abbastanza intelligente, non mi piacerebbe fare affidamento su di esso e userò la variante a).

La variante b) ha senso per me solo se hai un disperato bisogno di fare il risultato intermedio non disponibile dopo il corpo del loop. Ma non riesco a immaginare una situazione così disperata, comunque ...

EDIT: Jon Skeet ha fatto un ottimo punto, dimostrando che la dichiarazione di variabili all'interno di un ciclo può fare una vera differenza semantica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.