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.
quelle
Antworten:
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.
quelle
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.
quelle
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:
Ausgabe:
Der Unterschied besteht darin, dass alle Aktionen dieselbe
outer
Variable erfassen , aber jede ihre eigene separateinner
Variable hat.quelle
Outer
9 sein?Folgendes habe ich in .NET geschrieben und kompiliert.
Dies ist, was ich von .NET Reflector bekomme, wenn CIL wieder in Code gerendert wird.
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
WriteLine
Funktion 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 juststring 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.quelle
Dies ist ein Gotcha in VB.NET. Das Visual Basic-Ergebnis initialisiert die Variable in diesem Beispiel nicht neu:
Dies gibt beim ersten Mal 0 aus (Visual Basic-Variablen haben beim Deklarieren Standardwerte!), Aber
i
jedes Mal danach.Wenn Sie jedoch ein hinzufügen
= 0
, erhalten Sie das, was Sie erwarten könnten:quelle
Ich habe einen einfachen Test gemacht:
vs.
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º:
vs.
2º
Welches sind genau die gleichen wie das Ergebnis. Ist das nicht ein Beweis dafür, dass die beiden Codes dasselbe produzieren?
quelle
Es ist sprachabhängig - IIRC C # optimiert dies, sodass es keinen Unterschied gibt, aber JavaScript (zum Beispiel) übernimmt jedes Mal die gesamte Speicherzuweisung.
quelle
Ich würde immer A verwenden (anstatt mich auf den Compiler zu verlassen) und könnte auch Folgendes umschreiben:
Dies beschränkt sich immer noch auf
intermediateResult
den Bereich der Schleife, wird jedoch nicht bei jeder Iteration neu deklariert.quelle
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.
quelle
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.my
Schlüsselwort), C # und Java, um nur 5 zu nennen, die ich verwendet habe.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.
quelle
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.
quelle
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:
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.
quelle
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 ...
quelle
Nun, dafür könnte man immer einen Spielraum schaffen:
Auf diese Weise deklarieren Sie die Variable nur einmal und sie stirbt, wenn Sie die Schleife verlassen.
quelle
Ich habe immer gedacht, dass Sie Speicher verschwenden, wenn Sie Ihre Variablen innerhalb Ihrer Schleife deklarieren. Wenn Sie so etwas haben:
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:
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.
quelle
Ich denke, es hängt vom Compiler ab und es ist schwierig, eine allgemeine Antwort zu geben.
quelle
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
class
oderstruct
), bevorzuge ich Variante a (außerhalb).Grund: Reduzierung der Anzahl der ctor-dtor-Aufrufe.
quelle
Aus Sicht der Leistung ist draußen (viel) besser.
Ich habe beide Funktionen jeweils 1 Milliarde Mal ausgeführt. außerhalb () dauerte 65 Millisekunden. inside () dauerte 1,5 Sekunden.
quelle
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:
quelle
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
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 '.
quelle
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.
quelle
Das ist die bessere Form
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.
quelle
Versuchte dasselbe in Go und verglich die Compiler-Ausgabe
go tool compile -S
mit go 1.9.4Nulldifferenz gemäß Assembler-Ausgabe.
quelle
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
Innenschleifenkoffer
Ich habe die kompilierte Datei auf dem IntelliJ-Dekompiler überprüft und in beiden Fällen das gleiche erhalten
Test.class
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
Innenschleifenkoffer
Wenn Sie genau hinschauen, wird nur das
Slot
zugewiesenei
undintermediateResult
inLocalVariableTable
als Produkt ihrer Erscheinungsreihenfolge ausgetauscht. Der gleiche Unterschied im Steckplatz spiegelt sich in anderen Codezeilen wider.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
Null Arbeit dekompiliert
quelle
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.
quelle