Antworten:
In Ihrem Beispiel nehme ich an str
ist nicht außerhalb der verwendeten while
Schleife, sonst würden Sie nicht die Frage stellen, weil es in der Erklärung derwhile
der Schleife keine Option sein würde, da es nicht kompilieren würde.
Also, da str
ist nicht außerhalb der Schleife verwendet, der kleinste mögliche Spielraum für str
ist innerhalb der while - Schleife.
Die Antwort lautet also nachdrücklich, dass str
unbedingt innerhalb der while-Schleife deklariert werden sollte. Kein Wenn, kein Und, kein Aber.
Der einzige Fall, in dem diese Regel verletzt werden könnte, ist, wenn es aus irgendeinem Grund von entscheidender Bedeutung ist, dass jeder Taktzyklus aus dem Code herausgedrückt wird. In diesem Fall möchten Sie möglicherweise in Betracht ziehen, etwas in einem äußeren Bereich zu instanziieren und stattdessen wiederzuverwenden bei jeder Iteration eines inneren Bereichs erneut instanziieren. Dies gilt jedoch aufgrund der Unveränderlichkeit von Strings in Java nicht für Ihr Beispiel: Eine neue Instanz von str wird immer am Anfang Ihrer Schleife erstellt und muss am Ende weggeworfen werden gibt es dort keine optimierungsmöglichkeit.
EDIT: (fügt meinen Kommentar unten in die Antwort ein)
In jedem Fall besteht der richtige Weg darin, den gesamten Code richtig zu schreiben, eine Leistungsanforderung für Ihr Produkt festzulegen, Ihr Endprodukt an dieser Anforderung zu messen und die Dinge zu optimieren, wenn sie diese nicht erfüllen. Und was normalerweise passiert, ist, dass Sie an nur wenigen Stellen Möglichkeiten finden, einige nette und formale algorithmische Optimierungen bereitzustellen, die unser Programm dazu bringen, seine Leistungsanforderungen zu erfüllen, anstatt Ihre gesamte Codebasis zu durchlaufen und Dinge zu optimieren und zu hacken um hier und da Taktzyklen zu quetschen.
Ich habe den Bytecode dieser beiden (ähnlichen) Beispiele verglichen:
Schauen wir uns 1. Beispiel an :
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
nach javac Test.java
, javap -c Test
bringen Sie:
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
}
Schauen wir uns das 2. Beispiel an :
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
nach javac Test.java
, javap -c Test
bringen Sie:
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
}
Die Beobachtungen zeigen, dass es keinen Unterschied zwischen diesen beiden Beispielen gibt. Es ist das Ergebnis von JVM-Spezifikationen ...
Im Namen der besten Codierungspraxis wird jedoch empfohlen, die Variable im kleinstmöglichen Bereich zu deklarieren (in diesem Beispiel befindet sie sich innerhalb der Schleife, da dies der einzige Ort ist, an dem die Variable verwendet wird).
final
Liebhabern: erklären , str
wie final
in dem inside
Paket Fall auch keinen Unterschied macht =)
Das Deklarieren von Objekten im kleinsten Bereich verbessert die Lesbarkeit .
Die Leistung spielt für die heutigen Compiler keine Rolle. (In diesem Szenario)
Aus Wartungssicht ist die zweite Option besser.
Deklarieren und initialisieren Sie Variablen an derselben Stelle im engstmöglichen Rahmen.
Wie Donald Ervin Knuth sagte:
"Wir sollten kleine Wirkungsgrade vergessen, etwa in 97% der Fälle: Vorzeitige Optimierung ist die Wurzel allen Übels."
dh) Situation, in der ein Programmierer Leistungsüberlegungen das Design eines Codeteils beeinflussen lässt. Dies kann in einem Entwurf zur Folge hat, dass ist nicht so sauber , wie es hätte sein können oder Code, der falsch ist, da der Code ist kompliziert durch die Optimierung und der Programmierer durch abgelenkt zu optimieren .
Bitte springen Sie zur aktualisierten Antwort ...
Für diejenigen, die Wert auf Leistung legen, nehmen Sie das System.out heraus und begrenzen Sie die Schleife auf 1 Byte. Unter Verwendung von double (Test 1/2) und String (3/4) werden die verstrichenen Zeiten in Millisekunden mit Windows 7 Professional 64 Bit und JDK-1.7.0_21 angegeben. Bytecodes (auch unten für Test1 und Test2 angegeben) sind nicht identisch. Ich war zu faul, um mit veränderlichen und relativ komplexen Objekten zu testen.
doppelt
Test1 dauerte: 2710 ms
Test2 dauerte: 2790 ms
String (in den Tests einfach double durch string ersetzen)
Test3 dauerte: 1200 ms
Test4 dauerte: 3000 ms
Bytecode kompilieren und abrufen
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
}
Es ist wirklich nicht einfach, die Leistung mit allen JVM-Optimierungen zu vergleichen. Es ist jedoch etwas möglich. Besserer Test und detaillierte Ergebnisse in Google Caliper
Dies ist nicht identisch mit dem obigen Code. Wenn Sie nur eine Dummy-Schleife codieren, überspringt JVM diese, sodass Sie zumindest etwas zuweisen und zurückgeben müssen. Dies wird auch in der Caliper-Dokumentation empfohlen.
@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;
}
Zusammenfassung: declareBefore zeigt eine bessere Leistung an - wirklich winzig - und verstößt gegen das Prinzip des kleinsten Anwendungsbereichs. JVM sollte dies eigentlich für Sie tun
Eine Lösung für dieses Problem könnte darin bestehen, einen variablen Bereich bereitzustellen, der die while-Schleife einschließt:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
Sie werden automatisch de-referenziert, wenn der äußere Bereich endet.
Wenn Sie die str
After-the-while-Schleife (bereichsbezogen) nicht verwenden müssen, gilt die zweite Bedingung, d. H.
while(condition){
String str = calculateStr();
.....
}
ist besser, wenn Sie ein Objekt auf dem Stapel nur definieren, wenn das condition
wahr ist. Ich benutze es, wenn du es brauchst
Ich denke, die beste Ressource zur Beantwortung Ihrer Frage wäre der folgende Beitrag:
Unterschied zwischen der Deklaration von Variablen vor oder in der Schleife?
Nach meinem Verständnis wäre dieses Ding sprachabhängig. IIRC Java optimiert dies, sodass es keinen Unterschied gibt, aber JavaScript (zum Beispiel) übernimmt jedes Mal die gesamte Speicherzuweisung in der Schleife. Insbesondere in Java würde die zweite nach Abschluss der Profilerstellung schneller ausgeführt.
Wie viele Leute darauf hingewiesen haben,
String str;
while(condition){
str = calculateStr();
.....
}
ist NICHT besser als das:
while(condition){
String str = calculateStr();
.....
}
Deklarieren Sie also keine Variablen außerhalb ihres Gültigkeitsbereichs, wenn Sie sie nicht wiederverwenden ...
Wenn Sie String str außerhalb der wile-Schleife deklarieren, kann innerhalb und außerhalb der while-Schleife auf ihn verwiesen werden. Wenn Sie String str innerhalb der while-Schleife deklarieren, kann nur innerhalb der while-Schleife darauf verwiesen werden.
Variablen sollten so nahe wie möglich an ihrem Verwendungsort deklariert werden.
Dies erleichtert RAII (Resource Acquisition Is Initialization) .
Es hält den Umfang der Variablen eng. Dadurch kann der Optimierer besser arbeiten.
Laut Google Android Development Guide sollte der variable Umfang begrenzt sein. Bitte überprüfen Sie diesen Link:
Die str
Variable wird verfügbar sein und auch nach der Ausführung unter dem Code etwas Speicherplatz reservieren.
String str;
while(condition){
str = calculateStr();
.....
}
Die str
Variable ist nicht verfügbar und es wird auch der Speicher freigegeben, der str
im folgenden Code für die Variable zugewiesen wurde .
while(condition){
String str = calculateStr();
.....
}
Wenn wir dem zweiten folgen, wird dies sicherlich unseren Systemspeicher reduzieren und die Leistung steigern.
Die oben genannte Frage ist wirklich ein Programmierproblem. Wie möchten Sie Ihren Code programmieren? Wo muss auf die 'STR' zugegriffen werden? Es ist nicht sinnvoll, eine Variable zu deklarieren, die lokal als globale Variable verwendet wird. Grundlagen der Programmierung glaube ich.
Warnung für fast alle in dieser Frage: Hier ist ein Beispielcode, bei dem es innerhalb der Schleife auf meinem Computer mit Java 7 leicht 200-mal langsamer sein kann (und der Speicherverbrauch ist auch etwas anders). Es geht aber um Allokation und nicht nur um Umfang.
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);
}
}
Schlussfolgerung: Abhängig von der Größe der lokalen Variablen kann der Unterschied selbst bei nicht so großen Variablen sehr groß sein.
Nur um zu sagen, dass manchmal außerhalb oder innerhalb der Schleife eine Rolle spielt.
bigStuff[(int) (value % STUFF_SIZE)] = value;
(versuchen Sie einen Wert von 2147483649L)
Ich denke, die Größe des Objekts ist ebenfalls wichtig. In einem meiner Projekte hatten wir ein großes zweidimensionales Array deklariert und initialisiert, wodurch die Anwendung eine Ausnahme wegen Speichermangels auslöste. Wir haben stattdessen die Deklaration aus der Schleife verschoben und das Array zu Beginn jeder Iteration gelöscht.
Sie haben das Risiko, NullPointerException
dass Ihre calculateStr()
Methode null zurückgibt und Sie dann versuchen, eine Methode für str aufzurufen.
Vermeiden Sie generell Variablen mit einem Nullwert . Es ist übrigens stärker für Klassenattribute.
NullPointerException.
Falls dieser Code versucht , return str;
es würde ein Übersetzungsfehler auftreten.