Unterschied zwischen der Deklaration von Variablen vor oder in der Schleife?


312

Ich habe mich immer gefragt, ob es im Allgemeinen einen (Leistungs-) Unterschied macht, eine Wegwerfvariable vor einer Schleife zu deklarieren, anstatt sie wiederholt innerhalb der Schleife zu deklarieren. Ein (ziemlich sinnloses) Beispiel in Java:

a) Deklaration vor Schleife:

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

b) Deklaration (wiederholt) innerhalb der Schleife:

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

Welches ist besser, a oder b ?

Ich vermute, dass eine wiederholte Variablendeklaration (Beispiel b ) theoretisch mehr Overhead verursacht , aber dass Compiler klug genug sind, damit es keine Rolle spielt. Beispiel b hat den Vorteil, dass es kompakter ist und den Umfang der Variablen auf den Verwendungsort beschränkt. Trotzdem neige ich dazu, gemäß Beispiel a zu codieren .

Edit: Ich interessiere mich besonders für den Java-Fall.


Dies ist wichtig, wenn Sie Java-Code für die Android-Plattform schreiben. Google schlägt vor, dass zeitkritischer Code inkrementierende Variablen außerhalb einer for-Schleife wie innerhalb der for-Schleife jedes Mal in dieser Umgebung neu deklariert. Der Leistungsunterschied ist bei teuren Algorithmen sehr auffällig.
AaronCarson

1
@ AaronCarson könnten Sie bitte Link zu diesem Vorschlag von Google
Vitaly Zinchenko

Antworten:


256

Was ist besser, a oder b ?

Aus Sicht der Leistung müssten Sie es messen. (Und meiner Meinung nach ist der Compiler nicht sehr gut, wenn Sie einen Unterschied messen können).

Aus Wartungssicht ist b besser. Deklarieren und initialisieren Sie Variablen an derselben Stelle im engstmöglichen Rahmen. Lassen Sie keine Lücke zwischen der Deklaration und der Initialisierung und verschmutzen Sie keine Namespaces, die Sie nicht benötigen.


5
Anstelle von Double, wenn es sich um String handelt, ist der Fall "b" immer noch besser?
Antoops

3
@Antoops - Ja, b ist aus Gründen besser, die nichts mit dem Datentyp der deklarierten Variablen zu tun haben. Warum sollte es für Strings anders sein?
Daniel Earwicker

215

Nun, ich habe Ihre A- und B-Beispiele jeweils 20 Mal ausgeführt und 100 Millionen Mal wiederholt. (JVM - 1.5.0)

A: durchschnittliche Ausführungszeit: 0,074 Sek

B: durchschnittliche Ausführungszeit: 0,067 Sek

Zu meiner Überraschung war B etwas schneller. So schnell wie Computer jetzt sind, ist es schwer zu sagen, ob Sie dies genau messen können. Ich würde es auch so wie A codieren, aber ich würde sagen, dass es nicht wirklich wichtig ist.


12
Du hast mich geschlagen, ich wollte gerade meine Ergebnisse für die Profilerstellung veröffentlichen, ich habe mehr oder weniger die gleichen Ergebnisse erzielt und ja, überraschenderweise ist B schneller. Ich hätte wirklich gedacht, A, wenn ich darauf hätte wetten müssen.
Mark Davidson

14
Keine große Überraschung - wenn die Variable lokal in der Schleife ist, muss sie nicht nach jeder Iteration beibehalten werden, damit sie in einem Register verbleiben kann.

142
+1 für das tatsächliche Testen , nicht nur eine Meinung / Theorie, die sich das OP hätte ausdenken können.
MGOwen

3
@ GoodPerson um ehrlich zu sein, ich möchte, dass das gemacht wird. Ich habe diesen Test ungefähr 10 Mal auf meinem Computer für 50.000.000 bis 100.000.000 Iterationen mit fast identischem Code ausgeführt (den ich gerne mit jedem teilen würde, der Statistiken ausführen möchte). Die Antworten wurden in beiden Richtungen fast gleichmäßig aufgeteilt, normalerweise mit einem Abstand von 900 ms (über 50 Millionen Iterationen), was nicht wirklich viel ist. Obwohl mein erster Gedanke ist, dass es "Lärm" sein wird, könnte es sich nur ein bisschen lehnen. Diese Bemühungen scheinen mir jedoch rein akademisch zu sein (für die meisten realen Anwendungen). Ich würde sowieso gerne ein Ergebnis sehen;) Ist jemand einverstanden?
Javatarz

3
Das Anzeigen von Testergebnissen ohne Dokumentation des Setups ist wertlos. Dies gilt insbesondere in diesem Fall, in dem beide Codefragmente identischen Bytecode erzeugen, sodass jeder gemessene Unterschied nur ein Zeichen für unzureichende Testbedingungen ist.
Holger

66

Das hängt von der Sprache und der genauen Verwendung ab. Zum Beispiel machte es in C # 1 keinen Unterschied. Wenn in C # 2 die lokale Variable von einer anonymen Methode (oder einem Lambda-Ausdruck in C # 3) erfasst wird, kann dies einen sehr signifikanten Unterschied bewirken.

Beispiel:

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

Ausgabe:

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

Der Unterschied besteht darin, dass alle Aktionen dieselbe outerVariable erfassen , aber jede ihre eigene separate innerVariable hat.


3
Erstellt es in Beispiel B (ursprüngliche Frage) tatsächlich jedes Mal eine neue Variable? Was passiert in den Augen des Stapels?
Royi Namir

@ Jon, war es ein Fehler in C # 1.0? Sollte nicht idealerweise Outer9 sein?
Nawfal

@nawfal: Ich weiß nicht was du meinst. Lambda-Ausdrücke waren nicht in 1.0 ... und Outer ist 9. Welchen Fehler meinst du?
Jon Skeet

@nawfal: Mein Punkt ist, dass es in C # 1.0 keine Sprachfunktionen gab, bei denen man den Unterschied zwischen dem Deklarieren einer Variablen innerhalb einer Schleife und dem Deklarieren außerhalb einer Schleife erkennen konnte (vorausgesetzt, beide wurden kompiliert). Das hat sich in C # 2.0 geändert. Kein Fehler.
Jon Skeet

@ JonSkeet Oh ja, ich verstehe dich jetzt, ich habe völlig übersehen, dass du solche Variablen in 1.0 nicht schließen kannst, meine schlechte! :)
Nawfal

35

Folgendes habe ich in .NET geschrieben und kompiliert.

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

Dies ist, was ich von .NET Reflector bekomme, wenn CIL wieder in Code gerendert wird.

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

So sehen beide nach der Kompilierung genau gleich aus. In verwalteten Sprachen wird Code in CL / Byte-Code konvertiert und zum Zeitpunkt der Ausführung in Maschinensprache konvertiert. In der Maschinensprache wird möglicherweise nicht einmal ein Double auf dem Stapel erstellt. Es kann nur ein Register sein, da der Code widerspiegelt, dass es sich um eine temporäre Variable für die WriteLineFunktion handelt. Es gibt eine ganze Reihe von Optimierungsregeln nur für Schleifen. Der Durchschnittsbürger sollte sich also keine Sorgen machen, insbesondere in verwalteten Sprachen. Es gibt Fälle, in denen Sie den Verwaltungscode optimieren können, z. B. wenn Sie eine große Anzahl von Zeichenfolgen mit just string a; a+=anotherstring[i]vs using verketten müssenStringBuilder. Es gibt einen sehr großen Leistungsunterschied zwischen beiden. Es gibt viele solcher Fälle, in denen der Compiler Ihren Code nicht optimieren kann, weil er nicht herausfinden kann, was in einem größeren Bereich beabsichtigt ist. Aber es kann so ziemlich grundlegende Dinge für Sie optimieren.


int j = 0 für (; j <0x3e8; j ++) wird auf diese Weise einmal beide Variablen und nicht jede für den Zyklus deklariert. 2) Die Zuordnung ist fetter als alle anderen Optionen. 3) Die Best-Practice-Regel ist also jede Deklaration außerhalb der Iteration für.
Luka

24

Dies ist ein Gotcha in VB.NET. Das Visual Basic-Ergebnis initialisiert die Variable in diesem Beispiel nicht neu:

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

' Output: 0 1 2 3 4...

Dies gibt beim ersten Mal 0 aus (Visual Basic-Variablen haben beim Deklarieren Standardwerte!), Aber ijedes Mal danach.

Wenn Sie jedoch ein hinzufügen = 0, erhalten Sie das, was Sie erwarten könnten:

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
Ich benutze VB.NET seit Jahren und bin nicht darauf gestoßen !!
ChrisA

12
Ja, es ist unangenehm, dies in der Praxis herauszufinden.
Michael Haren

Hier ist ein Hinweis über die von Paul Vick: panopticoncentral.net/archive/2006/03/28/11552.aspx
ferventcoder

1
@eschneider @ferventcoder Leider hat @PaulV beschlossen, seine alten Blog-Beiträge zu löschen , daher ist dies jetzt ein toter Link.
Mark Hurd

ja, bin erst kürzlich darauf gestoßen; suchte nach offiziellen Dokumenten zu diesem Thema ...
Eric Schneider

15

Ich habe einen einfachen Test gemacht:

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

vs.

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

Ich habe diese Codes mit gcc - 5.2.0 kompiliert. Und dann habe ich das main () dieser beiden Codes zerlegt und das ist das Ergebnis:

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 

Welches sind genau die gleichen wie das Ergebnis. Ist das nicht ein Beweis dafür, dass die beiden Codes dasselbe produzieren?


3
Ja, und es ist cool, dass Sie dies getan haben, aber dies kommt auf das zurück, was die Leute über die Abhängigkeit von Sprache und Compiler gesagt haben. Ich frage mich, wie sich JIT oder interpretierte Sprachleistung auswirken würden.
user137717

12

Es ist sprachabhängig - IIRC C # optimiert dies, sodass es keinen Unterschied gibt, aber JavaScript (zum Beispiel) übernimmt jedes Mal die gesamte Speicherzuweisung.


Ja, aber das ist nicht viel. Ich habe einen einfachen Test mit einer for-Schleife durchgeführt, die 100 Millionen Mal ausgeführt wurde, und festgestellt, dass der größte Unterschied für die Deklaration außerhalb der Schleife 8 ms betrug. Es war normalerweise eher 3-4 und gelegentlich erklärte das Deklarieren außerhalb der Schleife SCHLECHTER (bis zu 4 ms), aber das war nicht typisch.
user137717

11

Ich würde immer A verwenden (anstatt mich auf den Compiler zu verlassen) und könnte auch Folgendes umschreiben:

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

Dies beschränkt sich immer noch auf intermediateResultden Bereich der Schleife, wird jedoch nicht bei jeder Iteration neu deklariert.


12
Möchten Sie konzeptionell, dass die Variable für die Dauer der Schleife lebt und nicht separat pro Iteration? Das mache ich selten. Schreiben Sie Code, der Ihre Absicht so klar wie möglich offenbart, es sei denn, Sie haben einen sehr, sehr guten Grund, etwas anderes zu tun.
Jon Skeet

4
Ah, schöner Kompromiss, daran habe ich nie gedacht! IMO, der Code wird allerdings etwas weniger "klar"
Rabarberski

2
@ Jon - Ich habe keine Ahnung, was das OP tatsächlich mit dem Zwischenwert macht. Ich dachte nur, es wäre eine erwägenswerte Option.
Triptychon

6

Meiner Meinung nach ist b die bessere Struktur. In a bleibt der letzte Wert von intermediärem Ergebnis erhalten, nachdem Ihre Schleife beendet wurde.

Bearbeiten: Dies macht bei Werttypen keinen großen Unterschied, aber Referenztypen können etwas gewichtig sein. Persönlich möchte ich, dass Variablen so schnell wie möglich zur Bereinigung dereferenziert werden, und b erledigt das für Sie.


sticks around after your loop is finished- obwohl dies in einer Sprache wie Python keine Rolle spielt, in der gebundene Namen bleiben, bis die Funktion endet.
new123456

@ new123456: Die OP fragte nach Einzelheiten Java, auch wenn die Frage war etwas allgemein gefragt. Viele von C abgeleitete Sprachen haben einen Gültigkeitsbereich auf Blockebene: C, C ++, Perl (mit dem mySchlüsselwort), C # und Java, um nur 5 zu nennen, die ich verwendet habe.
Powerlord

Ich weiß - es war eine Beobachtung, keine Kritik.
new123456

5

Ich vermute, einige Compiler könnten beide so optimieren, dass sie der gleiche Code sind, aber sicherlich nicht alle. Also würde ich sagen, dass Sie mit dem ersteren besser dran sind. Der einzige Grund für Letzteres ist, wenn Sie sicherstellen möchten, dass die deklarierte Variable nur in Ihrer Schleife verwendet wird.


5

In der Regel deklariere ich meine Variablen im innerstmöglichen Bereich. Wenn Sie IntermediateResult nicht außerhalb der Schleife verwenden, würde ich mich für B entscheiden.


5

Ein Mitarbeiter bevorzugt das erste Formular und gibt an, dass es sich um eine Optimierung handelt. Er zieht es vor, eine Deklaration wiederzuverwenden.

Ich bevorzuge den zweiten (und versuche meinen Kollegen zu überzeugen! ;-)), nachdem ich Folgendes gelesen habe:

  • Es reduziert den Umfang der Variablen auf den Ort, an dem sie benötigt werden, was gut ist.
  • Java optimiert genug, um keinen signifikanten Unterschied in der Leistung zu machen. IIRC, vielleicht ist die zweite Form noch schneller.

Auf jeden Fall fällt es in die Kategorie der vorzeitigen Optimierung, die von der Qualität des Compilers und / oder der JVM abhängt.


5

Es gibt einen Unterschied in C #, wenn Sie die Variable in einem Lambda usw. verwenden. Im Allgemeinen wird der Compiler jedoch im Grunde das Gleiche tun, vorausgesetzt, die Variable wird nur innerhalb der Schleife verwendet.

Da sie im Grunde genommen gleich sind: Beachten Sie, dass Version b den Lesern viel deutlicher macht, dass die Variable nach der Schleife nicht verwendet werden kann und kann. Darüber hinaus lässt sich Version b viel einfacher umgestalten. In Version a ist es schwieriger, den Schleifenkörper in eine eigene Methode zu extrahieren. Darüber hinaus versichert Ihnen Version b, dass ein solches Refactoring keine Nebenwirkungen hat.

Daher nervt mich Version a ohne Ende, weil es keinen Nutzen bringt und es viel schwieriger macht, über den Code nachzudenken ...


5

Nun, dafür könnte man immer einen Spielraum schaffen:

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

Auf diese Weise deklarieren Sie die Variable nur einmal und sie stirbt, wenn Sie die Schleife verlassen.


4

Ich habe immer gedacht, dass Sie Speicher verschwenden, wenn Sie Ihre Variablen innerhalb Ihrer Schleife deklarieren. Wenn Sie so etwas haben:

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

Dann muss nicht nur das Objekt für jede Iteration erstellt werden, sondern es muss auch eine neue Referenz für jedes Objekt zugewiesen werden. Es scheint, dass wenn der Garbage Collector langsam ist, Sie eine Reihe von baumelnden Referenzen haben, die bereinigt werden müssen.

Wenn Sie jedoch Folgendes haben:

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

Dann erstellen Sie nur eine einzelne Referenz und weisen ihr jedes Mal ein neues Objekt zu. Sicher, es kann etwas länger dauern, bis es außerhalb des Anwendungsbereichs liegt, aber dann gibt es nur einen baumelnden Hinweis, mit dem man sich befassen muss.


3
Nicht jedem Objekt wird eine neue Referenz zugewiesen, selbst wenn die Referenz in der 'for'-Schleife deklariert ist. In BEIDEN Fällen: 1) 'o' ist eine lokale Variable, für die zu Beginn der Funktion einmal Stapelspeicher zugewiesen wird. 2) In jeder Iteration wird ein neues Objekt erstellt. Es gibt also keinen Unterschied in der Leistung. Für die Codeorganisation, Lesbarkeit und Wartbarkeit ist es besser, die Referenz innerhalb der Schleife zu deklarieren.
Ajoy Bhatia

1
Während ich nicht für Java sprechen kann, wird in .NET die Referenz nicht für jedes Objekt im ersten Beispiel "zugewiesen". Auf dem Stapel befindet sich ein einzelner Eintrag für diese lokale Variable (für die Methode). Für Ihre Beispiele ist die erstellte IL identisch.
Jesse C. Slicer

3

Ich denke, es hängt vom Compiler ab und es ist schwierig, eine allgemeine Antwort zu geben.


3

Meine Praxis ist folgende:

  • Wenn der Variablentyp einfach ist (int, double, ...), bevorzuge ich Variante b (innen).
    Grund: Reduzierung des Variablenumfangs.

  • Wenn der Variablentyp nicht einfach ist (irgendeine Art von classoder struct), bevorzuge ich Variante a (außerhalb).
    Grund: Reduzierung der Anzahl der ctor-dtor-Aufrufe.


1

Aus Sicht der Leistung ist draußen (viel) besser.

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

Ich habe beide Funktionen jeweils 1 Milliarde Mal ausgeführt. außerhalb () dauerte 65 Millisekunden. inside () dauerte 1,5 Sekunden.


2
Muss dann eine nicht optimierte Debug-Kompilierung gewesen sein, oder?
Tomasz Przychodzki

int j = 0 für (; j <0x3e8; j ++) wird auf diese Weise einmal beide Variablen und nicht jede für den Zyklus deklariert. 2) Die Zuordnung ist fetter als alle anderen Optionen. 3) Die Best-Practice-Regel ist also jede Deklaration außerhalb der Iteration für.
Luka

1

Ich habe mit Node 4.0.0 auf JS getestet, wenn jemand interessiert ist. Das Deklarieren außerhalb der Schleife führte zu einer Leistungsverbesserung von ~ 0,5 ms im Durchschnitt über 1000 Versuche mit 100 Millionen Schleifeniterationen pro Versuch. Also werde ich sagen, mach weiter und schreibe es auf die lesbarste / wartbarste Art und Weise, nämlich B, imo. Ich würde meinen Code in eine Geige stecken, aber ich habe das Performance-Now-Node-Modul verwendet. Hier ist der Code:

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) ist eine sichere Wette als B) ......... Stellen Sie sich vor, Sie initialisieren die Struktur in einer Schleife und nicht 'int' oder 'float'. Was dann?

mögen

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

Sie werden sicherlich Probleme mit Speicherlecks haben!. Daher glaube ich, dass 'A' sicherer ist, während 'B' anfällig für Speicherakkumulation ist, insbesondere wenn enge Quellbibliotheken verwendet werden. Sie können die Verwendung des 'Valgrind'-Tools unter Linux überprüfen, insbesondere des Sub-Tools' Helgrind '.


0

Das ist eine interessante Frage. Aus meiner Erfahrung ist eine letzte Frage zu berücksichtigen, wenn Sie diese Angelegenheit für einen Kodex diskutieren:

Gibt es einen Grund, warum die Variable global sein muss?

Es ist sinnvoll, die Variable nur einmal global und nicht mehrmals lokal zu deklarieren, da dies für die Organisation des Codes besser ist und weniger Codezeilen erfordert. Wenn es jedoch nur lokal innerhalb einer Methode deklariert werden muss, würde ich es in dieser Methode initialisieren, damit klar ist, dass die Variable ausschließlich für diese Methode relevant ist. Achten Sie darauf, diese Variable nicht außerhalb der Methode aufzurufen, in der sie initialisiert wird, wenn Sie die letztere Option auswählen. Ihr Code weiß nicht, wovon Sie sprechen, und meldet einen Fehler.

Als Randnotiz sollten Sie auch keine lokalen Variablennamen zwischen verschiedenen Methoden duplizieren, auch wenn ihre Zwecke nahezu identisch sind. es wird nur verwirrend.


1
lol Ich bin aus so vielen Gründen anderer Meinung ... Allerdings keine Ablehnung ... Ich respektiere Ihr Wahlrecht
Grantly

0

Das ist die bessere Form

double intermediateResult;
int i = byte.MinValue;

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

1) auf diese Weise einmal beide Variablen und nicht jede für den Zyklus deklariert. 2) Die Zuordnung ist fetter als alle anderen Optionen. 3) Die Best-Practice-Regel ist also jede Deklaration außerhalb der Iteration für.


0

Versuchte dasselbe in Go und verglich die Compiler-Ausgabe go tool compile -Smit go 1.9.4

Nulldifferenz gemäß Assembler-Ausgabe.


0

Ich hatte lange die gleiche Frage. Also habe ich einen noch einfacheren Code getestet.

Schlussfolgerung: In solchen Fällen gibt es KEINEN Leistungsunterschied.

Außenschleifenkasten

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

Innenschleifenkoffer

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

Ich habe die kompilierte Datei auf dem IntelliJ-Dekompiler überprüft und in beiden Fällen das gleiche erhalten Test.class

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

Ich habe auch Code für beide Fälle mit der in dieser Antwort angegebenen Methode zerlegt . Ich werde nur die Teile zeigen, die für die Antwort relevant sind

Außenschleifenkasten

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;

Innenschleifenkoffer

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;

Wenn Sie genau hinschauen, wird nur das Slotzugewiesene iund intermediateResultin LocalVariableTableals Produkt ihrer Erscheinungsreihenfolge ausgetauscht. Der gleiche Unterschied im Steckplatz spiegelt sich in anderen Codezeilen wider.

  • Es wird keine zusätzliche Operation ausgeführt
  • intermediateResult ist in beiden Fällen immer noch eine lokale Variable, daher gibt es keinen Unterschied bei der Zugriffszeit.

BONUS

Compiler optimieren eine Menge, schauen Sie sich an, was in diesem Fall passiert.

Null Arbeitsfall

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

Null Arbeit dekompiliert

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

-1

Selbst wenn ich weiß, dass mein Compiler klug genug ist, möchte ich mich nicht darauf verlassen und werde die Variante a) verwenden.

Die Variante b) ist für mich nur dann sinnvoll, wenn Sie das IntermediateResult nach dem Loop-Body unbedingt nicht mehr verfügbar machen müssen . Aber ich kann mir eine so verzweifelte Situation sowieso nicht vorstellen ...

EDIT: Jon Skeet machte einen sehr guten Punkt und zeigte, dass die Variablendeklaration innerhalb einer Schleife einen tatsächlichen semantischen Unterschied machen kann.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.