Quando il parallelismo è utile
Fattori: carico di lavoro e dimensione dei dati
Quando il parallelismo è utile è una lezione Java Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Java Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Java Academy include 4 lezioni in totale.
Il parallelismo ha un costo
Passare all'esecuzione parallela aggiunge un sovraccarico: suddivisione dei dati, distribuzione delle attività e unione dei risultati. Conviene solo quando questo costo è inferiore al tempo risparmiato.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long sum = IntStream.rangeClosed(1, 10_000_000)
.parallel()
.asLongStream()
.sum();
System.out.println(sum);
}
}Fattore 1: dimensione dei dati (N)
Un valore N grande ammortizza il sovraccarico fisso del parallelismo. Una regola empirica approssimativa è che servano decine di migliaia di elementi prima che il parallelismo diventi conveniente.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long count = IntStream.rangeClosed(1, 5_000_000)
.parallel()
.filter(n -> n % 7 == 0)
.count();
System.out.println(count);
}
}Fattore 2: lavoro per elemento (Q)
Conta anche il costo Q dell'elaborazione di ogni elemento. Un lavoro costoso per elemento (calcoli intensivi) trae vantaggio dal parallelismo anche con valori di N più piccoli.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long primes = IntStream.rangeClosed(2, 200_000)
.parallel()
.filter(Main::isPrime)
.count();
System.out.println(primes);
}
static boolean isPrime(int n) {
for (int i = 2; (long) i * i <= n; i++)
if (n % i == 0) return false;
return true;
}
}L'intuizione di N per Q
Consideri il lavoro utile totale come N x Q. Più grande è questo prodotto, maggiore può essere il vantaggio del parallelismo. Valori molto piccoli di N o un valore trascurabile di Q raramente lo giustificano.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
double sum = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.mapToDouble(n -> Math.sqrt(n) * Math.log(n + 1))
.sum();
System.out.println(sum);
}
}Fattore 3: suddivisibilità
Le sorgenti di dati che si suddividono in modo rapido e uniforme si parallelizzano bene: array, ArrayList e IntStream.range. LinkedList e le sorgenti basate su iteratori si suddividono male.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) list.add(i);
long even = list.parallelStream().filter(n -> n % 2 == 0).count();
System.out.println(even);
}
}Buona sorgente: array e intervalli
Gli intervalli di valori primitivi hanno una dimensione nota e si suddividono in O(1), il che li rende sorgenti parallele ideali.
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) {
long sum = LongStream.rangeClosed(1, 20_000_000)
.parallel()
.sum();
System.out.println(sum);
}
}Sorgente scadente: generatori iterativi
Stream.iterate produce gli elementi in sequenza per definizione, quindi non può suddividerli finché gli elementi non sono stati materializzati. È una sorgente parallela poco efficace.
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
long count = Stream.iterate(1, n -> n + 1)
.limit(1_000_000)
.parallel()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);
}
}Fattore 4: fase di unione poco costosa
Il parallelismo richiede un modo economico per combinare i risultati parziali. sum e count si uniscono facilmente; creare una lista ordinata o usare un collector con una struttura ad albero complessa richiede un'unione costosa.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int max = IntStream.rangeClosed(1, 5_000_000)
.parallel()
.map(n -> n % 1000)
.max()
.getAsInt();
System.out.println(max);
}
}Eviti il lavoro vincolato dall'I/O
Gli stream paralleli sono pensati per attività vincolate dalla CPU eseguite nel pool fork-join. L'I/O bloccante priva il pool condiviso di risorse e danneggia l'intera applicazione. Per l'I/O usi invece executor dedicati.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long total = IntStream.rangeClosed(1, 1_000_000)
.parallel()
.mapToLong(n -> (long) n * n)
.sum();
System.out.println(total);
}
}Misuri, non supponga
L'unico modo affidabile per sapere se il parallelismo è utile è eseguire un benchmark con dati realistici. L'intuizione sulle prestazioni è spesso sbagliata.
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) {
long start = System.nanoTime();
long sum = LongStream.rangeClosed(1, 50_000_000).parallel().sum();
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.println("sum=" + sum + " took ~" + ms + "ms");
}
}Checklist pratica
Preferisca il parallelismo quando sono vere tutte queste condizioni:
- N grande e/o lavoro costoso per elemento.
- Una sorgente suddivisibile (array, ArrayList, intervallo).
- Un'unione economica e associativa.
- Un'attività vincolata dalla CPU, senza I/O bloccante.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
double avg = IntStream.rangeClosed(1, 10_000_000)
.parallel()
.mapToDouble(Math::sqrt)
.average()
.getAsDouble();
System.out.println(avg);
}
}Verifica rapida
Quale scenario è il candidato migliore per uno stream parallelo?
Riepilogo
Ha imparato quando il parallelismo è utile:
- Il vantaggio cresce con N x Q (dimensione dei dati moltiplicata per il costo per elemento).
- Serve una sorgente suddivisibile (array, ArrayList, intervalli).
- Serve un'unione economica e associativa.
- Mantenga il lavoro vincolato dalla CPU, eviti l'I/O bloccante e misuri sempre le prestazioni.
Domande Frequenti
La lezione «Quando il parallelismo è utile» è gratuita?
Sì — il testo completo di «Quando il parallelismo è utile» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Java Academy, passa a CoddyKit PRO. Il corso Java Academy include 4 lezioni in totale.
Cosa imparerò in «Quando il parallelismo è utile»?
Fattori: carico di lavoro e dimensione dei dati Eserciti Java Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Java Academy?
Non è richiesta alcuna esperienza precedente. Java Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.
Quanto tempo richiede la lezione «Quando il parallelismo è utile»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Java Academy?
Sì. Ogni lezione Java Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Creazione di stream paralleli
- Quando il parallelismo è utile
- Sicurezza dei thread ed effetti collaterali
- Problemi comuni