2015-06-05 15 views
8

Sono curioso di sapere se for..in deve essere preferito a .each per motivi di prestazioni.Groovy: è per..in significativamente più veloce di .each?

+2

Cosa hai provato fino ad ora? Nota che JMH (http://openjdk.java.net/projects/code-tools/jmh/) ha un template groovy che rende facile (ma non banale) creare un Benchmark in groovy. Assicurati di tornare con i risultati! – llogiq

+0

@llogiq grazie per aver menzionato jmh! Darò un colpo. –

+6

Per me cambiare tutti 'each' in' for-in' è probabilmente l'ultimo della lista delle tecniche di ottimizzazione. Solo dicendo :-) O per dirla in altro modo, una chiamata DB non necessaria può equivalere a 1000s di for-in ottimizzati. – defectus

risposta

4

For .. in fa parte del controllo di flusso della lingua standard.

Invece each chiama una chiusura con sovraccarico extra.

.each {...} è lo zucchero sintassi equivalente alla chiamata metodo .each({...})

Inoltre, a causa del fatto che si tratta di una chiusura, all'interno each blocco di codice non è possibile utilizzare break e continue istruzioni per controllare il ciclo.

http://kunaldabir.blogspot.it/2011/07/groovy-performance-iterating-with.html

punto di riferimento Aggiornamento Java 1.8.0_45 Groovy 2.4.3:

  • 3327981 ciascun {}
  • 320949 per() {

Ecco un altro punto di riferimento con 100000 iterazioni:

risultati:

  • 261062715 ogni {}
  • 64518703 for() {}
+2

questo è dati di quattro anni. non fidarti mai di un benchmark che non ti sei attrezzato. – cfrick

+0

hai ragione, ma è collegato un punto, tutti possono eseguire nuovamente il punto di riferimento. Lo faccio solo con java8 e il risultato è: 3056480/8181870/3327981/320949 – frhack

+0

Quanto è lungo l'elenco? – weston

4

Diamo uno sguardo teorico le cose in termini di ciò che chiamate sono fatte dinamica e quali chiamate sono fatte più direttamente con la logica Java (chiamerò quelle chiamate statiche).

In caso di for-in, Groovy funziona su un Iterator, per ottenerlo, abbiamo una chiamata dinamica a iteratore(). Se non sbaglio, hasNext e le chiamate successive vengono eseguite utilizzando la normale logica di chiamata del metodo Java. Quindi per ogni iterazione abbiamo qui solo 2 chiamate statiche. A seconda del benchmark, si deve notare che la prima chiamata iterator() può causare dei tempi di inizializzazione, poiché ciò potrebbe avviare il sistema di meta class e ciò richiede un momento.

In caso di each abbiamo la chiamata dinamica a ciascuno di essi, così come la creazione dell'oggetto per il blocco aperto (istanza di Chiusura). ciascuna (Chiusura) chiamerà anche iteratore(), ma senza cache ... beh tutto il costo una tantum. Durante il ciclo hasNext e next vengono eseguiti utilizzando la logica Java che effettua 2 chiamate statiche. La chiamata in istanza di chiusura viene eseguita con la logica standard java per la chiamata al metodo, che quindi invocherà doCall utilizzando una chiamata dinamica.

Per riassumere, per iterazione for-in utilizza solo 2 chiamate statiche, mentre each dispone di 3 chiamate statiche e 1 dinamica. La chiamata dinamica è molto più lenta di più chiamate statiche e molto più difficile da ottimizzare per la JVM, dominando così i tempi. Di conseguenza, each dovrebbe essere sempre più lento, a condizione che il blocco aperto richieda la chiamata dinamica.

A causa della complicata logica per la chiamata di chiusura # è difficile ottimizzare la chiamata dinamica. E questo è fastidioso, perché non è realmente necessario e verrà rimosso non appena troveremo una soluzione alternativa.Se dovessimo riuscirci, each potrebbe essere ancora più lento, ma è una cosa molto più difficile, dal momento che le dimensioni dei byte e i profili di invocazione svolgono un ruolo qui. In teoria potrebbero essere uguali (ignorando il tempo di avvio), ma la JVM ha molto più lavoro da fare. Ovviamente lo stesso vale per esempi di elaborazione lambda basata su stream in Java8,

+0

In Java8 forEach con chiusura sembra più veloce dello standard per ciclo. https://gist.github.com/frhack/5db0485f9847e6b673be – frhack

+0

@frhack devi stare attento con i microbenchmarks del genere. devi dargli un tempo di riscaldamento adeguato e la cosa migliore è testare solo un costrutto allo stesso tempo. Sono positivo che i tempi in entrambi sono molto simili se testati con questi vincoli. Ma ci sono ovviamente differenze nella dimensione del codice. La versione forEach ha il metodo for-each più il lambda che deve essere ottimizzato. La normale versione for-loop ha il corpo del loop, ma anche il metodo in cui il corpo del loop si trova per ottimizzare. Questo è il motivo per cui non è bello avere il ciclo semplice nel test con tutto il resto. – blackdrag

+0

Ne risulta una grande codifica per la parte da ottimizzare e può portare il compilatore a non farlo. Alla fine, forEach esegue gli stessi passi logici del ciclo normale, ma con più chiamate di metodo intermedie. Quindi ci vuole più tempo per ottimizzare ... ma alla fine entrambi dovrebbero essere uguali. Tutto il resto significherebbe un potenziale sprecato per me. Non c'è per me nessuna ragione tecnica che il tuo esempio debba comportarsi in modo diverso – blackdrag