2016-06-29 47 views
10

Da quando ha introdotto Java 8 mi sono davvero appassionato alle lambda e ho iniziato a usarle quando possibile, principalmente per iniziare ad abituarmi a loro. Uno degli usi più comuni è quando vogliamo iterare e agire su una collezione di oggetti, nel qual caso ricorro a forEach o stream(). Raramente scrivo il vecchio ciclo for(T t : Ts) e mi sono quasi dimenticato del for(int i = 0.....).Come decidere tra l'iterazione lambda e il ciclo normale?

Tuttavia, stavamo discutendo di questo con il mio supervisore l'altro giorno e mi ha detto che i lambda non sono sempre la scelta migliore e possono a volte ostacolare le prestazioni. Da una conferenza che ho visto su questo nuovo film ho avuto la sensazione che le iterazioni lambda siano sempre completamente ottimizzate dal compilatore e sarà (sempre?) Migliore delle semplici iterazioni, ma si chiede di dissentire. È vero? Se sì, come faccio a distinguere tra la migliore soluzione in ogni scenario?

P.S: Sono non parlando di casi in cui è consigliabile applicare parallelStream. Ovviamente quelli saranno più veloci.

+5

I flussi paralleli possono essere più lenti dei normali cicli se le richieste della CPU e/o le dimensioni del flusso non sono grandi. I thread sono costosi da gestire. – Bohemian

+1

Il sovraccarico del framework del flusso potrebbe essere sorprendente. Misurare! –

+0

In effetti e così ho aggiornato la mia domanda. – Konstantine

risposta

1

Dipende dall'implementazione specifica.

In generale il metodo forEach e il ciclo foreach su Iterator di solito hanno prestazioni piuttosto simili in quanto utilizzano un livello di astrazione simile. stream() di solito è più lento (spesso del 50-70%) poiché aggiunge un altro livello che fornisce l'accesso alla raccolta sottostante.

I vantaggi di stream() in genere sono il parallelismo possibile e il concatenamento facile delle operazioni con molti di quelli riusabili forniti da JDK.

+1

Al punto, quindi 'List.forEach' non paga la penalizzazione delle prestazioni, ma sfortunatamente non ha il potere espressivo di' List.stream /(). ForEach'. –

+1

Non sono d'accordo con la dichiarazione degli stream che è più lenta del 50-70%. Nella maggior parte dei casi, questi numeri derivano da parametri di riferimento errati che misurano il sovraccarico per la prima volta. – Holger

2

Le prestazioni dipendono da tanti fattori, che è difficile da prevedere. Normalmente, diremmo, se il vostro supervisore afferma che c'è stato un problema con le prestazioni, il vostro supervisore è incaricato di spiegare quale problema.

Una cosa di cui qualcuno potrebbe aver paura è che dietro le quinte viene generata una classe per ogni sito di creazione lambda (con l'implementazione corrente), quindi se il codice in questione viene eseguito solo una volta, questo potrebbe essere considerato un spreco di risorse. Questo si armonizza con il fatto che le espressioni lambda hanno un overhead di inizializzazione più elevato del codice imperativo ordinario (non stiamo confrontando qui le classi interne), quindi gli inizializzatori di classi all'interno, che vengono eseguiti solo una volta, potrebbero essere considerati evitabili. Questo è anche in linea con il fatto che dovresti never use parallel streams in class initializers, quindi questo potenziale vantaggio non è comunque disponibile qui.

Per codice ordinario, eseguito frequentemente che è probabilmente ottimizzato dalla JVM, questi problemi non si presentano. Come si suppone correttamente, le classi generate per le espressioni lambda ottengono lo stesso trattamento (ottimizzazioni) delle altre classi. In questi luoghi, chiamare forEach sulle raccolte ha il potenziale di essere più efficiente di di un ciclo for.

Le istanze di oggetti temporanei creati per Iterator o l'espressione lambda sono trascurabili, tuttavia, potrebbe essere interessante notare che un ciclo foreach sarà sempre creare un'istanza Iterator che lambda expression do not always do. Mentre l'implementazione default di Iterable.forEach crea anche uno Iterator, alcune delle raccolte più utilizzate sfruttano l'opportunità di fornire un'implementazione specializzata, in particolare ArrayList.

Il forEachArrayList è fondamentalmente un ciclo for su un array, senza alcun Iterator. Quindi invocherà il metodo accept dello Consumer, che sarà una classe generata contenente una delega banale al metodo sintetico contenente il codice dell'espressione lambda. Per ottimizzare l'intero ciclo, l'orizzonte dell'ottimizzatore deve estendersi sul loop di ArrayList su un array (un linguaggio comune riconoscibile per un ottimizzatore), il metodo sintetico accept contenente una delega banale e il metodo contenente il codice effettivo.

Al contrario, quando l'iterazione nello stesso elenco utilizzando un ciclo foreach, un Iterator attuazione viene creata contenente la ArrayList iterazione logica, si sviluppa su due metodi, hasNext() e next() e istanza variabili del Iterator. Il loop ripetutamente richiamare il metodo hasNext() controlla la condizione finale (index<size) e next() che riverificare la condizione prima di ritornare l'elemento, in quanto non v'è alcuna garanzia che il chiamante non correttamente invoca hasNext() prima next(). Ovviamente, un ottimizzatore è in grado di rimuovere questa duplicazione, ma ciò richiede uno sforzo maggiore rispetto al non averlo in primo luogo. Quindi, per ottenere le stesse prestazioni del metodo forEach, l'orizzonte dell'ottimizzatore deve estendere il codice loop, l'implementazione non banale hasNext() e l'implementazione non banale next().

Le cose simili possono essere applicate anche ad altre raccolte con un'implementazione specializzata forEach. Questo vale anche per le operazioni di streaming, se la sorgente fornisce un'implementazione specializzata Spliterator, che non diffonde la logica di iterazione su due metodi come uno Iterator.

Quindi, se si desidera discutere gli aspetti tecnici di for ciascuno contro forEach(…), è possibile utilizzare queste informazioni.

Tuttavia, come detto, questi aspetti descrivono solo potenziali aspetti di prestazioni poiché il lavoro dell'ottimizzatore e altri aspetti ambientali di runtime possono modificare completamente l'esito. Penso che, come regola generale, più piccolo è il corpo/azione del loop, più appropriato è il metodo forEach. Questo si armonizza perfettamente con la linea guida di evitare espressioni lambda troppo lunghe comunque.

Problemi correlati