Dovrei sempre usare un flusso parallelo quando possibile?


Con Java 8 e lambda è facile scorrere le raccolte come flussi e altrettanto facile usare un flusso parallelo. Due esempi da i documenti, il secondo utilizzando parallelStream:

myShapesCollection.stream()
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

myShapesCollection.parallelStream() // <-- This one uses parallel
    .filter(e -> e.getColor() == Color.RED)
    .forEach(e -> System.out.println(e.getName()));

Finché non mi interessa l'ordine, sarebbe sempre utile usare il parallelo? Si potrebbe pensare che sia più veloce dividendo il lavoro su più core.

Ci sono altre considerazioni? Quando dovrebbe essere usato il flusso parallelo e quando dovrebbe essere il non parallelo usato?

(Questa domanda viene posta per innescare una discussione su come e quando utilizzare i flussi paralleli, non perché penso che usarli sempre sia una buona idea.)

Author: Matsemann, 2013-12-04

4 answers

Un flusso parallelo ha un overhead molto più alto rispetto a uno sequenziale. Coordinare i thread richiede una quantità significativa di tempo. Userei i flussi sequenziali per impostazione predefinita e considererei solo quelli paralleli se

  • Ho una quantità enorme di elementi da elaborare (o l'elaborazione di ciascun elemento richiede tempo ed è parallelizzabile)

  • Ho un problema di prestazioni in primo luogo

  • Non eseguo già il processo in un multi-thread ambiente (ad esempio: in un contenitore web, se ho già molte richieste da elaborare in parallelo, l'aggiunta di un ulteriore livello di parallelismo all'interno di ogni richiesta potrebbe avere effetti più negativi che positivi)

Nel tuo esempio, le prestazioni saranno comunque guidate dall'accesso sincronizzato a System.out.println() e rendere parallelo questo processo non avrà alcun effetto, o anche negativo.

Inoltre, ricorda che i flussi paralleli non risolvono magicamente tutte le problemi di sincronizzazione. Se una risorsa condivisa viene utilizzata dai predicati e dalle funzioni utilizzate nel processo, dovrai assicurarti che tutto sia thread-safe. In particolare, gli effetti collaterali sono cose di cui devi davvero preoccuparti se vai in parallelo.

In ogni caso, misura, non indovinare! Solo una misurazione ti dirà se il parallelismo vale la pena o meno.

 500
Author: JB Nizet, 2017-05-10 03:07:16

L'API Stream è stata progettata per semplificare la scrittura dei calcoli in un modo che è stato astratto dal modo in cui sarebbero stati eseguiti, rendendo facile il passaggio tra sequenziale e parallelo.

Tuttavia, solo perché è facile, non significa che sia sempre una buona idea, e in effetti, è una cattiva idea di far cadere .parallel() in tutto il luogo semplicemente perché puoi.

Innanzitutto, si noti che il parallelismo non offre alcun vantaggio se non la possibilità di un'esecuzione più rapida quando più i nuclei sono disponibili. Un'esecuzione parallela comporterà sempre più lavoro di uno sequenziale, perché oltre a risolvere il problema, deve anche eseguire l'invio e il coordinamento di sotto-compiti. La speranza è che sarai in grado di arrivare alla risposta più velocemente interrompendo il lavoro su più processori; se ciò accade effettivamente dipende da molte cose, inclusa la dimensione del tuo set di dati, la quantità di calcolo che stai facendo su ogni elemento, la natura del calcolo (in particolare, l'elaborazione di un elemento interagisce con l'elaborazione di altri?), il numero di processori disponibili e il numero di altre attività concorrenti per tali processori.

Inoltre, si noti che il parallelismo spesso espone anche il non determinismo nel calcolo che è spesso nascosto da implementazioni sequenziali; a volte questo non ha importanza, o può essere mitigato limitando le operazioni coinvolte (cioè, gli operatori di riduzione devono essere apolidi e associativi.)

In realtà, a volte il parallelismo accelererà il tuo calcolo, a volte non lo farà, e talvolta lo rallenterà persino. È meglio sviluppare prima utilizzando l'esecuzione sequenziale e quindi applicare il parallelismo dove (A) si sa che c'è effettivamente beneficio per aumentare le prestazioni e (B) che effettivamente fornirà prestazioni aumentate. (A) è un problema commerciale, non tecnico. Se sei un esperto di prestazioni, di solito sarai in grado di guardare il codice e determinare (B), ma il percorso intelligente è quello di misurare. (E, non preoccuparti nemmeno finché non sei convinto di (A); se il codice è abbastanza veloce, meglio applicare i tuoi cicli cerebrali altrove.)

Il modello di prestazioni più semplice per il parallelismo è il modello "NQ", dove N è il numero di elementi e Q è il calcolo per elemento. In generale, è necessario che il prodotto NQ superi una certa soglia prima di iniziare a ottenere un vantaggio in termini di prestazioni. Per un problema a basso Q come " sommare i numeri da 1 a N", generalmente vedrai un pareggio tra N=1000 e N=10000. Con problemi di Q superiore, vedrai breakevens a soglie inferiori.

Ma la realtà è piuttosto complicata. Quindi, fino a quando non raggiungi l'esperienza, prima identifica quando l'elaborazione sequenziale ti costa qualcosa e poi misura se il parallelismo ti aiuterà.

 176
Author: Brian Goetz, 2018-02-24 16:45:43

Ho visto una delle presentazioni di Brian Goetz (Java Language Architect & specification lead per le espressioni Lambda) . Egli spiega in dettaglio i seguenti 4 punti da considerare prima di andare per la parallelizzazione:

Costi di divisione / decomposizione
- A volte la divisione è più costosa del semplice lavoro!
Costi di spedizione / gestione delle attività
- Può fare un sacco di lavoro nel tempo necessario per lavorare a mano ad un altro thread.
Costi di combinazione dei risultati
- A volte combinazione comporta la copia di un sacco di dati. Ad esempio, l'aggiunta di numeri è economica mentre l'unione di set è costosa.
Località
- L'elefante nella stanza. Questo è un punto importante che tutti possono perdere. Dovresti considerare gli errori di cache, se una CPU attende i dati a causa degli errori di cache, non otterrai nulla dalla parallelizzazione. Ecco perché le fonti basate su array parallelizzano il meglio come i prossimi indici (vicino all'indice corrente) vengono memorizzati nella cache e ci sono meno possibilità che la CPU subisca una mancanza di cache.

Menziona anche una formula relativamente semplice per determinare una possibilità di accelerazione parallela.

Modello NQ :

N x Q > 10000

dove,
N = numero di voci di dati
Q = quantità di lavoro per articolo

 40
Author: Ram Patra, 2016-08-21 17:40:37

JB ha colpito il chiodo sulla testa. L'unica cosa che posso aggiungere è che Java8 non fa pura elaborazione parallela, fa paraquenziale Sì, ho scritto l'articolo e ho fatto F/J per trent'anni, quindi capisco il problema.

 11
Author: edharned, 2013-12-04 19:39:33