Aller au contenu principal

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à existantIntStream.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.