Parallélisable
Parrallélisable signifie que le traitement d'une tâche peut être divisé en plusieurs sous-tâches exécutées en même temps, souvent sur plusieurs cœurs de processeur.
En programmation, ça veut dire quoi ?
Prenons une liste de données à traiter :
- Si tu traites chaque élément l’un après l’autre, c’est séquentiel.
- Si tu peux traiter plusieurs éléments en même temps, c’est parallélisable.
Exemple en Java avec Streams
list.stream() // exécution séquentielle
.map(e -> ... )
.collect(...);
list.parallelStream() // exécution parallèle
.map(e -> ... )
.collect(...);
Avec parallelStream(), Java essaie de répartir le travail sur plusieurs threads. Si tu as un processeur avec 4 coeurs, il pourrait traiter 4 morceaux de la liste en parallèle.
Une tâche est parallélisable si :
- Elle peut être découpée en morceaux indépendants.
- Ces morceaux peuvent être traités sans ordre spécifique.
- Il n’y a pas de partage d’état critique entre les sous-tâches.
À faire attention
- Le traitement parallèle n’est pas toujours plus rapide, surtout pour de petites listes.
- Il peut y avoir des problèmes si ton code a des effets de bord (ex. : écrire dans un fichier ou modifier une variable globale).
- Il faut aussi considérer la surcharge du parallélisme (création/gestion des threads).
En résumé :
Parallélisable = "Peut être exécuté en plusieurs morceaux, en même temps, sans dépendances bloquantes entre les morceaux."
Bonne situation
Pour que le parallélisme soit réellement bénéfique, l'opération par élément doit être CPU-intensive. Ici, on vérifie si chaque nombre est premier — un calcul lourd par élément.
import java.util.*;
import java.util.stream.*;
import java.time.*;
public class TestParallele {
static boolean estPremier(long n) {
if (n < 2) return false;
for (long i = 2; i <= Math.sqrt(n); i++)
if (n % i == 0) return false;
return true;
}
public static void main(String[] args) {
List<Long> liste = new Random().longs(5_000_000, 1_000_000, 10_000_000)
.boxed()
.collect(Collectors.toList());
Instant debut1 = Instant.now();
liste.stream()
.map(TestParallele::estPremier)
.collect(Collectors.toList());
Instant fin1 = Instant.now();
System.out.println("Séquentiel : " + Duration.between(debut1, fin1).toMillis() + " ms");
Instant debut2 = Instant.now();
liste.parallelStream()
.map(TestParallele::estPremier)
.collect(Collectors.toList());
Instant fin2 = Instant.now();
System.out.println("Parallèle : " + Duration.between(debut2, fin2).toMillis() + " ms");
}
}
Mauvaise situation
import java.util.stream.IntStream;
public class MauvaisParallele {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
// Avec stream parallèle — provoque parfois des erreurs ou résultats incorrects
IntStream.range(0, 1000)
.parallel()
.forEach(i -> sb.append(i));
System.out.println("Résultat (parallèle avec effet de bord) : " + sb.length());
}
}
Problèmes ici :
- StringBuilder n'est pas thread-safe.
- Plusieurs threads essaient de modifier le même objet → data races et résultats incorrects.
- Et même si on synchronisait, ça ralentirait à cause de la contention.
.parallel() vs .parallelStream()
Ce sont deux façons d'arriver au même résultat :
| Sur quoi c'est appelé | Exemple | |
|---|---|---|
.parallelStream() | Une Collection (List, Set...) | list.parallelStream() |
.parallel() | Un Stream déjà existant | IntStream.range(0, 1000).parallel() |
IntStream.range() retourne un IntStream séquentiel — comme ce n'est pas une Collection, il n'a pas de méthode parallelStream(). On chaîne donc .parallel() pour le convertir en stream parallèle.
En pratique :
- Tu as une Collection → utilise
parallelStream() - Tu as un Stream déjà créé (
IntStream,LongStream,Stream.of()...) → utilise.parallel()
Le comportement final est identique dans les deux cas.