C'è una differenza di prestazioni tra un ciclo for e un ciclo for-each?


184

Qual è l'eventuale differenza di prestazioni tra i due loop seguenti?

for (Object o: objectArrayList) {
    o.DoSomething();
}

e

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}

@Keparo: è un ciclo "per ogni" non un ciclo "for-in"
Ande Turner,

in java si chiama "per ciascuno", ma quando si tratta di Obiettivo C si chiama ciclo "per In".
dannazione,


L'esteso per le prestazioni del ciclo è discusso qui: stackoverflow.com/questions/12155987/...
Eckes

Anche se ci fosse una differenza, sarebbe così minore che dovresti favorire la leggibilità a meno che questo pezzo di codice non venga eseguito trilioni di volte al secondo. E poi avresti bisogno di un benchmark adeguato per evitare comunque l'ottimizzazione prematura.
Zabuzard,

Risposte:


212

Dall'articolo 46 in Java efficace di Joshua Bloch:

Il ciclo for-each, introdotto nella versione 1.5, elimina il disordine e la possibilità di errore nascondendo completamente l'iteratore o la variabile indice. Il linguaggio risultante si applica ugualmente alle raccolte e alle matrici:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

Quando vedi i due punti (:), leggilo come "in". Pertanto, il ciclo sopra si legge come "per ogni elemento e negli elementi". Si noti che non vi è alcuna penalità prestazionale per l'utilizzo del ciclo for-each, anche per gli array. In effetti, in alcune circostanze può offrire un leggero vantaggio in termini di prestazioni rispetto a un normale ciclo for, poiché calcola il limite dell'indice di array solo una volta. Mentre puoi farlo manualmente (Articolo 45), i programmatori non lo fanno sempre.


48
Vale la pena ricordare che in un ciclo for-each non c'è modo di accedere a un contatore indice (poiché non esiste)
basszero

Sì, ma quel contatore è ora visibile al di fuori del ciclo. Certo, è una soluzione semplice ma lo è anche per ciascuno!
Indolente il

74
C'è una penalità prestazionale nell'allocare l'iteratore. Avevo un codice molto parallelo in uno sfondo animato Android. Ho visto che il cestino della spazzatura stava impazzendo. È stato perché i loop for-each stavano allocando iteratori temporanei in molti thread diversi (di breve durata), causando un sacco di lavoro al garbage collector. Il passaggio a normali cicli basati su indice ha risolto il problema.
gsingh2011,

1
@ gsingh2011 Ma questo dipende anche se stai usando un elenco di accesso casuale o meno. L'uso dell'accesso basato sull'indice agli elenchi di accesso non casuali sarà molto peggio dell'uso per ciascuno con elenchi di accesso casuali, immagino. Se stai lavorando con l'interfaccia Elenco e quindi non conosci l'attuale tipo di implementazione, puoi verificare se l'elenco è un'istanza di (implementa) RandomAccess, se ti interessa davvero tanto: docs.oracle.com/javase/8 /docs/api/java/util/RandomAccess.html
Puce

3
@ gsingh2011 I documenti Android ( developer.android.com/training/articles/perf-tips.html#Loops ) menzionano che avrai una penalità prestazionale quando usi foreach su un ArrayList, non su altre raccolte. Sono curioso di sapere se quello è stato il tuo caso.
Viccari,

29

Tutti questi loop fanno esattamente lo stesso, voglio solo mostrarli prima di buttare i miei due centesimi.

Innanzitutto, il modo classico di scorrere l'elenco:

for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }

In secondo luogo, il modo preferito in quanto è meno soggetto a errori (quante volte hai fatto la cosa "oops, mescolando le variabili i e j in questi loop all'interno dei loop")?

for (String s : strings) { /* do something using s */ }

In terzo luogo, il micro-ottimizzato per loop:

int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }

Ora i due centesimi effettivi: almeno quando stavo testando questi, il terzo era il più veloce quando contavo millisecondi su quanto tempo impiegava per ogni tipo di loop con una semplice operazione ripetuta alcuni milioni di volte - questo stava usando Java 5 con jre1.6u10 su Windows nel caso qualcuno fosse interessato.

Anche se almeno sembra che il terzo sia il più veloce, dovresti davvero chiederti se vuoi correre il rischio di implementare questa ottimizzazione spioncino ovunque nel tuo codice di loop poiché da quello che ho visto, l'effettivo loop non è ' Di solito è la parte che richiede più tempo di qualsiasi programma reale (o forse sto solo lavorando sul campo sbagliato, chi lo sa). E anche come ho menzionato nel pretesto per il ciclo Java for-each (alcuni si riferiscono ad esso come ciclo Iterator e altri come ciclo for-in ) è meno probabile che colpisca quel particolare stupido bug quando lo si utilizza. E prima di discutere su come questo possa anche essere più veloce degli altri, ricorda che javac non ottimizza affatto il bytecode (beh, quasi per lo meno), lo compila e basta.

Se sei interessato alla micro-ottimizzazione e / o il tuo software utilizza molti loop ricorsivi e simili, potresti essere interessato al terzo tipo di loop. Ricorda solo di fare un benchmark del tuo software bene sia prima che dopo aver cambiato i for per questo strano e micro-ottimizzato.


5
Si noti che il ciclo for con ++ i <= size è "1-based", ad es. Il metodo get all'interno del loop verrà chiamato per i valori 1, 2, 3, ecc.
volley,

15
Un modo migliore per scrivere il ciclo micro-ottimizzato è per (int i = 0, size = strings.size (); ++ i <= size;) {} Questo è preferibile perché riduce al minimo l'ambito delle dimensioni
Dónal

1
il terzo non parte da i = 1 la prima volta che passa attraverso il ciclo, saltando il primo elemento. ed essendo un ciclo for non è necessario. int n = strings.length; while (n -> 0) {System.out.println ("" + n + "" + stringhe [n]); }
Lassi Kinnunen,

1
@ Dónal quel modello di loop manca il primo e dà un IOOBE. Questo funziona: per (int i = -1, size = list.size (); ++ i <size;)
Nathan Adams il

1
"Tutti questi loop fanno esattamente lo stesso" non è corretto. Uno utilizza l'accesso casuale get(int), l'altro utilizza un Iterator. Considera LinkedListdove le prestazioni for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }sono di gran lunga peggiori dal momento che sta facendo get(int)n volte.
Steve Kuo,

13

Il ciclo for-each dovrebbe essere generalmente preferito. L'approccio "get" potrebbe essere più lento se l'implementazione dell'elenco che si sta utilizzando non supporta l'accesso casuale. Ad esempio, se si utilizza un LinkedList, si incorre in un costo trasversale, mentre l'approccio for-each utilizza un iteratore che tiene traccia della sua posizione nell'elenco. Maggiori informazioni sulle sfumature del ciclo for-each .

Penso che l'articolo sia ora qui: nuova posizione

Il link mostrato qui era morto.


12

Bene, l'impatto sulle prestazioni è per lo più insignificante, ma non è zero. Se guardi JavaDoc RandomAccessdell'interfaccia:

Come regola generale, un'implementazione dell'elenco dovrebbe implementare questa interfaccia se, per le istanze tipiche della classe, questo ciclo:

for (int i=0, n=list.size(); i < n; i++)
    list.get(i);

corre più veloce di questo ciclo:

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

E per ogni ciclo sta usando la versione con iteratore, quindi ArrayListper esempio, per ogni ciclo non è più veloce.


Davvero ciascuno? Anche uno con un array? Ho letto qui stackoverflow.com/questions/1006395/… che non comporta un iteratore.
Ondra Žižka,

@ OndraŽižka: il ciclo for-each utilizza gli iteratori quando si esegue il loop su Iterables, ma non gli array. Per gli array viene utilizzato un ciclo for con una variabile indice. Ci sono informazioni al riguardo nel JLS .
Lii,

sì, quindi dovresti prima crearlo in un array con toArray, con un costo.
Lassi Kinnunen,

6

Sfortunatamente sembra esserci una differenza.

Se guardi il codice byte generato per entrambi i tipi di loop, sono diversi.

Ecco un esempio dal codice sorgente Log4j.

In /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java abbiamo una classe interna statica chiamata Log4jMarker che definisce:

    /*
     * Called from add while synchronized.
     */
    private static boolean contains(final Marker parent, final Marker... localParents) {
        //noinspection ForLoopReplaceableByForEach
        for (final Marker marker : localParents) {
            if (marker == parent) {
                return true;
            }
        }
        return false;
    }

Con anello standard:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: iconst_0
       1: istore_2
       2: aload_1
       3: arraylength
       4: istore_3
       5: iload_2
       6: iload_3
       7: if_icmpge     29
      10: aload_1
      11: iload_2
      12: aaload
      13: astore        4
      15: aload         4
      17: aload_0
      18: if_acmpne     23
      21: iconst_1
      22: ireturn
      23: iinc          2, 1
      26: goto          5
      29: iconst_0
      30: ireturn

Con per-ciascuno:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: arraylength
       4: istore_3
       5: iconst_0
       6: istore        4
       8: iload         4
      10: iload_3
      11: if_icmpge     34
      14: aload_2
      15: iload         4
      17: aaload
      18: astore        5
      20: aload         5
      22: aload_0
      23: if_acmpne     28
      26: iconst_1
      27: ireturn
      28: iinc          4, 1
      31: goto          8
      34: iconst_0
      35: ireturn

Cosa succede con QUESTO Oracle?

Ho provato questo con Java 7 e 8 su Windows 7.


7
Per coloro che stanno provando a leggere lo smontaggio, il risultato netto è che il codice generato all'interno del ciclo è identico, ma la configurazione for-each sembra aver creato una variabile temporanea aggiuntiva contenente un riferimento al secondo argomento. Se la variabile extra nascosta viene registrata, ma il parametro stesso non è durante la generazione del codice, il for-each sarebbe più veloce; se il parametro è registrato nell'esempio for (;;), il tempo di esecuzione sarebbe identico. Devo benchmark?
Robin Davies,

4

È sempre meglio usare l'iteratore invece di indicizzare. Questo perché l'iteratore è molto probabilmente ottimizzato per l'implementazione dell'elenco mentre l'indicizzazione (chiamata get) potrebbe non esserlo. Ad esempio, LinkedList è un elenco ma l'indicizzazione attraverso i suoi elementi sarà più lenta dell'iterazione mediante l'iteratore.


10
Penso che non ci sia qualcosa di "sempre" nelle ottimizzazioni delle prestazioni.)
bene

4

foreach rende più chiara l'intenzione del tuo codice e questo è normalmente preferito a un miglioramento della velocità molto piccolo, se presente.

Ogni volta che vedo un ciclo indicizzato, devo analizzarlo un po 'più a lungo per assicurarmi che faccia quello che penso . Ad esempio, inizia da zero, include o esclude il punto finale ecc.?

Sembra che la maggior parte del mio tempo passi a leggere il codice (che ho scritto o scritto da qualcun altro) e la chiarezza è quasi sempre più importante della performance. Oggigiorno è facile rinunciare alle prestazioni perché Hotspot fa un lavoro così straordinario.


4

Il seguente codice:

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

interface Function<T> {
    long perform(T parameter, long x);
}

class MyArray<T> {

    T[] array;
    long x;

    public MyArray(int size, Class<T> type, long x) {
        array = (T[]) Array.newInstance(type, size);
        this.x = x;
    }

    public void forEach(Function<T> function) {
        for (T element : array) {
            x = function.perform(element, x);
        }
    }
}

class Compute {
    int factor;
    final long constant;

    public Compute(int factor, long constant) {
        this.factor = factor;
        this.constant = constant;
    }

    public long compute(long parameter, long x) {
        return x * factor + parameter + constant;
    }
}

public class Main {

    public static void main(String[] args) {
        List<Long> numbers = new ArrayList<Long>(50000000);
        for (int i = 0; i < 50000000; i++) {
            numbers.add(i * i + 5L);
        }

        long x = 234553523525L;

        long time = System.currentTimeMillis();
        for (int i = 0; i < numbers.size(); i++) {
            x += x * 7 + numbers.get(i) + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        time = System.currentTimeMillis();
        for (long i : numbers) {
            x += x * 7 + i + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        numbers = null;
        MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return x * 8 + parameter + 5L;
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
        myArray = null;
        myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return new Compute(8, 5).compute(parameter, x);
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
    }
}

Fornisce il seguente output sul mio sistema:

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

Sto eseguendo Ubuntu 12.10 alpha con OracleJDK 1.7 aggiornamento 6.

In generale HotSpot ottimizza molte indirette e semplici operazioni ridondanti, quindi in generale non dovresti preoccuparti di loro a meno che non ce ne siano molte in sequenza o che siano pesantemente annidate.

D'altra parte, l'indicizzazione get su LinkedList è molto più lenta rispetto alla chiamata successiva su iteratore per LinkedList, in modo da poter evitare questo impatto sulle prestazioni mantenendo la leggibilità quando si utilizzano iteratori (esplicitamente o implicitamente in ogni ciclo).


3

Anche con qualcosa come ArrayList o Vector, in cui "get" è una semplice ricerca di array, il secondo loop ha ancora un sovraccarico aggiuntivo rispetto al primo. Mi aspetterei che sia un po 'più lento del primo.


Anche il primo ciclo deve ottenere ogni elemento. Crea un iteratore dietro le quinte per farlo. Sono davvero equivalenti.
Bill the Lizard,

Pensando in termini di C, un iteratore può semplicemente incrementare un puntatore, ma un get dovrebbe moltiplicare il valore di i per la larghezza di un puntatore ogni volta.
Paul Tomblin,

Dipende dal tipo di elenco che usi. Penso che tu abbia ragione, usare get non sarebbe mai più veloce, e talvolta più lento.
Bill the Lizard,


3

Ecco una breve analisi della differenza evidenziata dal team di sviluppo Android:

https://www.youtube.com/watch?v=MZOf3pOAM6A

Il risultato è che ci sia una differenza, e in ambienti molto contenuti con liste molto grandi potrebbe essere una differenza notevole. Nel loro test, il per ogni ciclo ha impiegato il doppio del tempo. Tuttavia, il loro test era su un arraylist di 400.000 numeri interi. La differenza effettiva per elemento nell'array era di 6 microsecondi . Non ho testato e non hanno detto, ma mi aspetto che la differenza sia leggermente più grande usando oggetti piuttosto che primitivi, ma anche a meno che tu non stia costruendo un codice di libreria in cui non hai idea della scala di ciò che ti verrà chiesto per iterare, penso che non valga la pena sottolineare la differenza.


2

Con il nome della variabile objectArrayList, suppongo che sia un'istanza dijava.util.ArrayList . In tal caso, la differenza di prestazioni sarebbe impercettibile.

D'altra parte, se si tratta di un'istanza di java.util.LinkedList, il secondo approccio sarà molto più lento di quelloList#get(int) è un'operazione O (n).

Quindi il primo approccio è sempre preferito a meno che l'indice non sia necessario per la logica nel loop.


1
1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

2. for(int i=0; i<objectArrayList.size(); i++){
    objectArrayList.get(i).DoSomthing();
}

Entrambi fanno lo stesso, ma per un utilizzo facile e sicuro della programmazione per ciascuno, ci sono possibilità di errori nel secondo modo di utilizzo.


1

È strano che nessuno abbia menzionato l'ovvio: foreach alloca la memoria (sotto forma di iteratore), mentre un normale ciclo for non alloca memoria. Per i giochi su Android, questo è un problema, perché significa che il Garbage Collector verrà eseguito periodicamente. In un gioco non vuoi che il Garbage Collector funzioni ... MAI. Quindi non usare foreach loop nel tuo metodo draw (o render).


1

La risposta accettata risponde alla domanda, a parte il caso eccezionale di ArrayList ...

Poiché la maggior parte degli sviluppatori si affida ad ArrayList (almeno credo di si)

Quindi sono obbligato ad aggiungere la risposta corretta qui.

Direttamente dalla documentazione per gli sviluppatori: -

Il ciclo avanzato per (noto anche come ciclo "per ogni") può essere utilizzato per raccolte che implementano l'interfaccia Iterable e per le matrici. Con le raccolte, viene assegnato un iteratore per effettuare chiamate di interfaccia a hasNext () e next (). Con una ArrayList, un ciclo contato scritto a mano è circa 3 volte più veloce (con o senza JIT), ma per altre raccolte la sintassi avanzata per il ciclo sarà esattamente equivalente all'uso esplicito dell'iteratore.

Esistono diverse alternative per iterare attraverso un array:

static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
    }
}

zero () è il più lento, poiché JIT non può ancora ottimizzare il costo di ottenere la lunghezza dell'array una volta per ogni iterazione attraverso il ciclo.

one () è più veloce. Estrae tutto nelle variabili locali, evitando le ricerche. Solo la lunghezza dell'array offre un vantaggio in termini di prestazioni.

two () è il più veloce per i dispositivi senza un JIT e indistinguibile da uno () per i dispositivi con un JIT. Utilizza la sintassi avanzata per loop introdotta nella versione 1.5 del linguaggio di programmazione Java.

Pertanto, è consigliabile utilizzare il ciclo avanzato per impostazione predefinita, ma considerare un ciclo contato scritto a mano per l'iterazione ArrayList critica per le prestazioni.


-2

Sì, la for-eachvariante è più veloce del normale index-based-for-loop.

for-eachusi variante iterator. Quindi l'attraversamento è più veloce del normale forciclo basato sull'indice.
Questo perché iteratorsono ottimizzati per l'attraversamento, perché punta appena prima dell'elemento successivo e subito dopo l'elemento precedente . Uno dei motivi per index-based-for-loopessere lento è che deve calcolare e spostarsi nella posizione dell'elemento ogni volta che non è coniterator .


-3
public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.