Risposte:
Nel tuo esempio presumo str
è non utilizzato al di fuori del while
ciclo, altrimenti non sarebbe chiedere la domanda, perché dichiarando all'interno delwhile
ciclo non sarebbe un'opzione, in quanto non sarebbe la compilazione.
Pertanto, poiché nonstr
viene utilizzato al di fuori del ciclo, l'ambito più piccolo possibile è nel ciclo while.str
Quindi, la risposta è decisamente che str
dovrebbe assolutamente essere dichiarata nel ciclo while. Nessun se, nessun e, nessun ma.
L'unico caso in cui questa regola potrebbe essere violata è se per qualche motivo è di vitale importanza che ogni ciclo di clock debba essere eliminato dal codice, nel qual caso potresti voler considerare l'istanza di qualcosa in un ambito esterno e riutilizzarla invece di ri-istanziandolo su ogni iterazione di un ambito interno. Tuttavia, questo non si applica al tuo esempio, a causa dell'immutabilità delle stringhe in Java: una nuova istanza di str verrà sempre creata all'inizio del tuo loop e dovrà essere gettata via alla fine di esso, quindi lì non c'è possibilità di ottimizzare lì.
EDIT: (iniettando il mio commento qui sotto nella risposta)
In ogni caso, il modo giusto di fare le cose è scrivere tutto il tuo codice in modo corretto, stabilire un requisito prestazionale per il tuo prodotto, misurare il tuo prodotto finale rispetto a questo requisito e, se non lo soddisfa, quindi andare a ottimizzare le cose. E ciò che di solito accade è che trovi modi per fornire alcune ottimizzazioni algoritmiche belle e formali in un paio di posti che fanno sì che il nostro programma soddisfi i suoi requisiti di prestazioni invece di dover andare su tutta la tua base di codice e modificare e hackerare le cose in per spremere i cicli di clock qua e là.
Ho confrontato il codice byte di quei due (simili) esempi:
Diamo un'occhiata a 1. esempio :
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
dopo javac Test.java
, javap -c Test
otterrai:
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
}
Diamo un'occhiata a 2. esempio :
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
dopo javac Test.java
, javap -c Test
otterrai:
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
}
Le osservazioni mostrano che non vi è alcuna differenza tra questi due esempi. È il risultato delle specifiche JVM ...
Ma nel nome della migliore pratica di codifica si consiglia di dichiarare la variabile nel più piccolo ambito possibile (in questo esempio è all'interno del ciclo, poiché questo è l'unico posto in cui viene utilizzata la variabile).
final
amanti: dichiarando str
come final
nel inside
caso del pacchetto anche non fa differenza =)
La dichiarazione di oggetti nel più piccolo ambito migliora la leggibilità .
Le prestazioni non contano per i compilatori di oggi. (In questo scenario)
Dal punto di vista della manutenzione, la seconda opzione è migliore.
Dichiarare e inizializzare le variabili nello stesso posto, nell'ambito più ristretto possibile.
Come diceva Donald Ervin Knuth :
"Dovremmo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali"
cioè) situazione in cui un programmatore lascia che considerazioni sulle prestazioni influenzino la progettazione di un pezzo di codice. Questo può portare ad un disegno che è non pulite come avrebbe potuto essere o codice che non è corretto, perché il codice è complicata dalla ottimizzazione e il programmatore è distratto da ottimizzare .
Passa alla risposta aggiornata ...
Per coloro che si preoccupano delle prestazioni, estrarre System.out e limitare il loop a 1 byte. Utilizzando double (test 1/2) e String (3/4) i tempi trascorsi in millisecondi sono indicati di seguito con Windows 7 Professional 64 bit e JDK-1.7.0_21. I bytecode (indicati anche di seguito per test1 e test2) non sono gli stessi. Ero troppo pigro per provare con oggetti mutabili e relativamente complessi.
Doppio
Test1 ha impiegato: 2710 msec
Test2 ha impiegato: 2790 msec
String (basta sostituire double con string nei test)
Test3 ha richiesto: 1200 msecs
Test4 ha richiesto: 3000 msec
Compilazione e ottenere bytecode
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
}
Non è davvero facile confrontare le prestazioni con tutte le ottimizzazioni JVM. Tuttavia, è in qualche modo possibile. Test migliori e risultati dettagliati in Google Caliper
Questo non è identico al codice sopra. Se semplicemente codifichi un loop fittizio, JVM lo salta, quindi almeno devi assegnare e restituire qualcosa. Questo è consigliato anche nella documentazione di Caliper.
@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;
}
Riepilogo: dichiarato Prima indica prestazioni migliori - davvero minuscole - ed è contro il più piccolo principio di portata. JVM dovrebbe effettivamente farlo per te
Una soluzione a questo problema potrebbe essere quella di fornire un ambito variabile che incapsuli il ciclo while:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
Saranno automaticamente de-referenziati al termine dell'oscilloscopio esterno.
Se non è necessario utilizzare il str
ciclo after the while (relativo all'ambito), quindi la seconda condizione, ad es
while(condition){
String str = calculateStr();
.....
}
è meglio se si definisce un oggetto nello stack solo se condition
è vero. Lo uso se ne hai bisogno
Penso che la migliore risorsa per rispondere alla tua domanda sia il seguente post:
Differenza tra la dichiarazione di variabili prima o in loop?
Secondo la mia comprensione questa cosa sarebbe dipendente dalla lingua. IIRC Java lo ottimizza, quindi non c'è alcuna differenza, ma JavaScript (ad esempio) eseguirà l'intera allocazione di memoria ogni volta nel ciclo. In Java, in particolare, penso che il secondo sarebbe più veloce quando si eseguiva il profiling.
Come molte persone hanno sottolineato,
String str;
while(condition){
str = calculateStr();
.....
}
NON è meglio di questo:
while(condition){
String str = calculateStr();
.....
}
Quindi non dichiarare le variabili al di fuori dei loro ambiti se non le stai riutilizzando ...
La dichiarazione di String str al di fuori del ciclo wile consente di fare riferimento all'interno e all'esterno del ciclo while. Dichiarare String str all'interno del ciclo while consente di fare riferimento solo al ciclo while.
Le variabili devono essere dichiarate il più vicino possibile al luogo di utilizzo.
Rende più semplice RAII (Resource Acquisition Is Initialization) .
Mantiene stretto l'ambito della variabile. Ciò consente all'ottimizzatore di funzionare meglio.
Secondo la Guida allo sviluppo di Google Android, l'ambito variabile dovrebbe essere limitato. Si prega di controllare questo link:
La str
variabile sarà disponibile e riserverà un po 'di spazio in memoria anche dopo essere stata eseguita sotto il codice.
String str;
while(condition){
str = calculateStr();
.....
}
La str
variabile non sarà disponibile e anche la memoria verrà rilasciata che è stata allocata per la str
variabile nel codice seguente.
while(condition){
String str = calculateStr();
.....
}
Se seguissimo sicuramente il secondo, ciò ridurrebbe la memoria del nostro sistema e aumenterà le prestazioni.
In verità, la domanda sopra esposta è un problema di programmazione. Come vorresti programmare il tuo codice? Dove è necessario accedere a "STR"? Non è possibile dichiarare una variabile utilizzata localmente come variabile globale. Nozioni di base di programmazione credo.
Avviso per quasi tutti in questa domanda: ecco un codice di esempio in cui all'interno del ciclo può essere facilmente 200 volte più lento sul mio computer con Java 7 (e anche il consumo di memoria è leggermente diverso). Ma si tratta di allocazione e non solo portata.
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);
}
}
Conclusione: a seconda della dimensione della variabile locale, la differenza può essere enorme, anche con variabili non così grandi.
Solo per dire che a volte, al di fuori o all'interno del ciclo è importante.
bigStuff[(int) (value % STUFF_SIZE)] = value;
(prova un valore di 2147483649L)
Penso che contino anche le dimensioni dell'oggetto. In uno dei miei progetti, avevamo dichiarato e inizializzato un grande array bidimensionale che stava facendo sì che l'applicazione generasse un'eccezione di memoria insufficiente. Abbiamo invece spostato la dichiarazione fuori dal ciclo e cancellato l'array all'inizio di ogni iterazione.
Si rischia NullPointerException
se il calculateStr()
metodo restituisce null e quindi si tenta di chiamare un metodo su str.
Più in generale, evitare di avere variabili con un valore nullo . A proposito, è più forte per gli attributi di classe.
NullPointerException.
Se questo codice tentasse di return str;
riscontrare un errore di compilazione.