Odpowiedzi:
W twoim przykładzie zakładam, że niestr
jest używany poza pętlą, w przeciwnym razie nie zadajesz pytania, ponieważ zadeklarowanie go w pętli nie byłoby opcją, ponieważ nie skompilowałoby się.while
while
A zatem, ponieważ niestr
jest używany poza pętlą, najmniejszy możliwy zakres dla znajduje się w pętli while.str
Tak więc odpowiedź jest zdecydowanie, że str
absolutnie należy zadeklarować w pętli while. Bez ifs, bez ands, bez ale.
Jedynym przypadkiem, w którym zasada ta może zostać naruszona, jest to, że z jakiegoś powodu niezwykle ważne jest, aby każdy cykl zegara był wyciskany z kodu, w którym to przypadku możesz rozważyć utworzenie instancji czegoś w zakresie zewnętrznym i ponowne użycie go zamiast ponowne utworzenie go przy każdej iteracji zakresu wewnętrznego. Nie dotyczy to jednak twojego przykładu ze względu na niezmienność ciągów w Javie: nowa instancja str będzie zawsze tworzona na początku twojej pętli i będzie musiała zostać wyrzucona na jej końcu, więc tam nie ma tam możliwości optymalizacji.
EDYCJA: (wstawiam mój komentarz poniżej w odpowiedzi)
W każdym razie właściwym sposobem jest poprawne napisanie całego kodu, ustalenie wymagań dotyczących wydajności produktu, porównanie produktu końcowego z tym wymaganiem, a jeśli nie spełnia tego warunku, optymalizacja. I zwykle dzieje się tak, że w kilku miejscach można znaleźć dobre i formalne optymalizacje algorytmiczne, które sprawiają, że nasz program spełnia wymagania dotyczące wydajności, zamiast konieczności przeglądania całej bazy kodu oraz poprawiania i hakowania aby wycisnąć cykle zegara tu i tam.
Porównałem bajtowy kod tych dwóch (podobnych) przykładów:
Spójrzmy na 1. przykład :
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
po javac Test.java
, javap -c Test
otrzymasz:
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
}
Spójrzmy na 2. przykład :
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
po javac Test.java
, javap -c Test
otrzymasz:
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
}
Obserwacje pokazują, że nie ma różnicy między tymi dwoma przykładami. Jest to wynik specyfikacji JVM ...
Jednak w imię najlepszej praktyki kodowania zaleca się zadeklarowanie zmiennej w możliwie najmniejszym zakresie (w tym przykładzie znajduje się ona w pętli, ponieważ jest to jedyne miejsce, w którym zmienna jest używana).
final
kochankowie: deklarowanie str
jak final
w inside
przypadku paczki również nie ma znaczenia =)
Deklarowanie obiektów w najmniejszym zakresie poprawia czytelność .
Wydajność nie ma znaczenia dla dzisiejszych kompilatorów. (W tym scenariuszu)
Z punktu widzenia konserwacji druga opcja jest lepsza.
Deklaruj i inicjalizuj zmienne w tym samym miejscu, w możliwie najwęższym zakresie.
Jak powiedział Donald Ervin Knuth :
„Powinniśmy zapomnieć o małej wydajności, powiedzmy około 97% czasu: przedwczesna optymalizacja jest źródłem wszelkiego zła”
tj. sytuacja, w której programista pozwala, aby względy wydajności wpływały na projekt fragmentu kodu. Może to spowodować, że projekt jest nie tak czysty, jak to mogło być albo kod, który jest nieprawidłowy, ponieważ kod jest skomplikowane przez optymalizację i programista jest rozproszony przez optymalizację .
Przejdź do zaktualizowanej odpowiedzi ...
Dla tych, którym zależy na wydajności, wyjmij System.out i ogranicz pętlę do 1 bajtu. Używając podwójnego (test 1/2) i ciąg (3/4), czasy, które upłynęły w milisekundach, podano poniżej dla Windows 7 Professional 64 bit i JDK-1.7.0_21. Kody bajtowe (podane również poniżej dla testu 1 i testu 2) nie są takie same. Byłem zbyt leniwy, by testować zmienne i stosunkowo złożone obiekty.
podwójnie
Test1 zajął: 2710 ms
Test2 zajął: 2790 ms
Ciąg (po prostu zamień podwójnie na ciąg w testach)
Test3 zajął: 1200 ms
Test4 zajął: 3000 ms
Kompilowanie i pobieranie kodu bajtowego
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
}
Naprawdę nie jest łatwo porównać wydajność ze wszystkimi optymalizacjami JVM. Jest to jednak w pewnym stopniu możliwe. Lepszy test i szczegółowe wyniki w Google Caliper
Nie jest to identyczne z powyższym kodem. Jeśli po prostu kodujesz sztuczną pętlę, JVM pomija ją, więc przynajmniej musisz coś przypisać i zwrócić. Jest to również zalecane w dokumentacji Calipera.
@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;
}
Podsumowanie: zadeklarowany Przed oznacza lepszą wydajność - naprawdę niewielką - i jest sprzeczny z zasadą najmniejszego zakresu. JVM powinien to zrobić za Ciebie
Jednym z rozwiązań tego problemu może być zapewnienie zmiennego zakresu enkapsulującego pętlę while:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
Zostaną one automatycznie usunięte z odniesienia po zakończeniu zakresu zewnętrznego.
Jeśli nie musisz używać str
pętli while (związanej z zakresem), to drugi warunek, tj
while(condition){
String str = calculateStr();
.....
}
jest lepszy, ponieważ jeśli zdefiniujesz obiekt na stosie, tylko jeśli condition
jest to prawda. Używam go, jeśli go potrzebujesz
Myślę, że najlepszym źródłem odpowiedzi na twoje pytanie będzie następujący post:
Różnica między deklarowaniem zmiennych przed lub w pętli?
Według mojego zrozumienia, ta rzecz byłaby zależna od języka. IIRC Java optymalizuje to, więc nie ma żadnej różnicy, ale JavaScript (na przykład) zajmie się przydzielaniem całej pamięci za każdym razem w pętli. W Javie szczególnie myślę, że drugi działałby szybciej po zakończeniu profilowania.
Jak zauważyło wiele osób,
String str;
while(condition){
str = calculateStr();
.....
}
NIE jest lepsze niż to:
while(condition){
String str = calculateStr();
.....
}
Więc nie deklaruj zmiennych poza ich zasięgiem, jeśli nie używasz ich ponownie ...
Zadeklarowanie ciągu stru na zewnątrz pętli wile umożliwia odwołanie do niej wewnątrz i na zewnątrz pętli while. Deklaracja ciągu str w pętli while pozwala na odwołanie się do niej tylko w pętli while.
Zmienne powinny być deklarowane tak blisko miejsca, w którym są używane, jak to możliwe.
Ułatwia to RAII (Resource Acquisition Is Initialization) .
Utrzymuje wąski zakres zmiennej. Dzięki temu optymalizator działa lepiej.
Według Przewodnika programistycznego Google Android zakres zmiennych powinien być ograniczony. Sprawdź ten link:
str
Zmienna będzie dostępna i zastrzeżone trochę miejsca w pamięci nawet po chwili wykonywany poniżej kodu.
String str;
while(condition){
str = calculateStr();
.....
}
str
Zmienna nie będzie dostępna, a także pamięć zostanie zwolniony, który został przydzielony do str
zmiennej w poniżej kodu.
while(condition){
String str = calculateStr();
.....
}
Jeśli zastosujemy się do drugiego, z pewnością zmniejszy to pamięć systemową i zwiększy wydajność.
Naprawdę, powyższe pytanie jest kwestią programistyczną. Jak chcesz zaprogramować swój kod? Gdzie potrzebujesz dostępu do „STR”? Nie ma sensu deklarować zmiennej, która jest używana lokalnie jako zmienna globalna. Podstawy programowania wierzę.
Ostrzeżenie dla prawie wszystkich osób w tym pytaniu: Oto przykładowy kod, w którym wewnątrz pętli może być 200 razy wolniejszy na moim komputerze z Javą 7 (a zużycie pamięci jest również nieco inne). Ale dotyczy alokacji, a nie tylko zakresu.
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);
}
}
Wniosek: w zależności od wielkości zmiennej lokalnej różnica może być ogromna, nawet przy niezbyt dużych zmiennych.
Wystarczy powiedzieć, że czasami na zewnątrz lub wewnątrz pętli ma znaczenie.
bigStuff[(int) (value % STUFF_SIZE)] = value;
(Wypróbuj wartość 2147483649L)
Myślę, że rozmiar obiektu również ma znaczenie. W jednym z moich projektów zadeklarowaliśmy i zainicjowaliśmy dużą dwuwymiarową tablicę, która powodowała, że aplikacja generowała wyjątek braku pamięci. Zamiast tego usunęliśmy deklarację z pętli i wyczyściliśmy tablicę na początku każdej iteracji.
Istnieje ryzyko, NullPointerException
że calculateStr()
metoda zwróci null, a następnie spróbujesz wywołać metodę na str.
Mówiąc bardziej ogólnie, unikaj posiadania zmiennych o wartości zerowej . Nawiasem mówiąc, jest silniejszy dla atrybutów klasy.
NullPointerException.
że próba wykonania tego kodu return str;
napotka błąd kompilacji.