Vous êtes sur la page 1sur 102

Diviser pour calculer plus vite

Fork - Join

riveill@unice.fr
h.p://www.i3s.unice.fr/~riveill

Comment mesurer l’accélera=on due
au parallélisme

2
Accéléra=on maximum à loi d’Amdhal

•  p pourcentage du code exécutable en parallèle


•  (1 – p) : pourcentage du code exécuté en séquen=el
•  p / n : exécu=on du code parallèle sur n cœurs

•  Le temps d’exécu=on théorique évolue donc en :


•  T = (1 - p) + p / n

•  Accélera=on A = 1/T
•  A = 1/((1 – p) + p/n)
•  Si n → ∞ alors A → 1/(1 – p)

3
Quelques exemples issues du projet
•  Nombre de threads entre 2^0 e 2^9
•  Pourcentage du code exécuté en parallèle
•  Chez certains P = 0,05
•  Chez d’autres P = 0,95
•  Temps d’exécu=on théorique
•  Temps pour faire sor=r une personne du terrain constant en moyenne : t
•  Temps pour faire sor=r X personnes en séquen=el : X*t
•  Temps pour faire sor=r X personnes en parallèle : T(X) = ((1-p) + p/n) * X * t
•  Les mesures doivent être entre les 2 bornes (et le plus proche possible de 100
% de parallélisme)
•  avec 4 coeurs, et p = 100 % à T(X) = X*t / 4
•  avec 4 coeurs et p = 0 % à T(X) = X*t
•  Pour éliminer t que l’on ne connait pas, on peut présenter les résultats sous la
forme d’un ra=o
•  Intérêt des mesures : est-ce que l’observa=on est conforme à ce que l’on
a.end
•  On présente les résultats sur des courbes
•  On essaie de trouver des explica=ons raisonnable

4
Applica=on numérique (sur d’autres données)

Application numérique :
§  p = 0,25
§  a = 3,7 à 32 cœurs
§  a → 4 quand n → ∞

§  p = 0,95
§  a = 12,55 à 32 cœurs
§  a = 17,42 à 128 cœurs
§  a = 20 quand n → ∞

§  p = 0,96
§  a = 14,28 à 32 cœurs : +12.1%,
§  a = 21,05 à 128 cœurs : +17.2%
§  a = 25 quand n → ∞

⇒ Ça vaut le coup de se battre pour


paralléliser quelques pourcents

5
Comment augmenter le parallélisme

Op=on 1 : réduire la taille des sec=ons cri=ques


•  Passer du verrouillage à gros grain (coarse-grained locking)
èà un verrouillage à grain fin (fine-grained locking)
•  Effet : augmente le p dans la loi d’Amdahl
•  Comment : diviser les longues sec=ons cri=ques par de nombreuses pe=tes
sec=on cri=ques
•  Augmente généralement la complexité du programme
•  Travail nécessaire, complexe
•  Mais pas vraiment de technique générale pour le faire…
•  Par conséquent pas de quoi faire une séance de cours sauf à vous donner des
sec=ons cri=ques à reécrire.
•  Op=on 2 : améliorer les algorithmes de verrouillage
•  Cf cours TMPC en SI5
•  Op=on 3 : supprimer les verrous en adoptant une algorithmique non
bloquante
•  Cf cours de la semaine prochaine (introduc=on) + cours TMPC en SI5
•  Op=on 4 : changer complètement le modèle mémoire avec les mémoires
transac=onnelles
•  Cf cours TMPC en SI5 6
Solu=ons naturelles

1.  Prendre les verrous les plus fins possibles...


c c c Fine grained parallelism
c The reason
has huge performance
we get
c benefit
only 2.9 speedup
c
Coarse c Fine
Grained Grained c c c
25% c 25%
c c
Shared c c c Shared

c c c c
c c 75% c c 75%
Unshared c c Unshared
c c
c c c c
7
A Concurrent FIFO Queue

Simple Code, easy to prove


correct

Head Tail

Object lock

a b c d

P: Dequeue() => a Q: Enqueue(d)

Contention and sequential bottleneck


Fine Grain Locks

Finer Granularity, More Complex Code

Head Tail

a b c d

P: Dequeue() => a Q: Enqueue(d)

Verification nightmare: worry about deadlock, livelock…


Complex boundary cases: empty queue, last item
Worry how to acquire multiple locks
A vous d’imaginer une solu=on pour la
première / seconde version du projet

10
Dis=nguer les tâches à faire et les
threads qui vont les exécuter

11
Un peu d’algorithmique pour commencer
•  Jusqu’à présent on a l’associa=on
•  1 tache est directement associé à 1 thread
•  Et on a autant de thread que de tache à exécuter

•  Nous pouvons maintenant poser des ques=ons de nature plus algorithmique :


•  Comment obtenir un gain de performance en adaptant le découpage tache /
thread et en op=misant le nombre de thread

•  Supposons que l’on souhaite addi=onner point par point deux tableaux de
même taille :
// Semantics: C = A + B
static void add (int[] C, int[] A, int[] B) {
for (int i = 0; i < C.length; i++)
C[i] = A[i] + B[i] ;
}
•  La complexité est O(n).

•  Si on dispose de plusieurs processeurs, on peut espérer faire mieux, car les


instruc=ons C[i] = A[i] + B[i] sont indépendantes les unes des autres.
•  Les architectures GPU sont parfaitement adaptées pour ce type de calcul
•  Présentée peut être dans le cadre de ce cours
12
Un parallélisme forcené ?

•  Une approche radicale consisterait à lancer n threads en parallèle :


static void add (int[] C, int[] A, int[] B) {
for (int i = 0; i < C.length; i++) {
fork { C[i] = A[i] + B[i]; }
}
joinAll ;
}
•  Est-ce une bonne idée ?
•  D’un point de vue pra=que, non, ce n’est pas une bonne idée.
•  Créer un thread coute cher
•  Il faut au grand minimum plusieurs milliers d’instruc=ons pour lancer
et arrêter un processus « léger ».
•  Lancer un thread pour effectuer une tache très courte n’a aucun sens !

13
Une version moins agressive

•  On peut lancer un nombre de threads moins important en découpant le


tableau en une série d’intervalles :
static void addChunk (int[] C, int[] A, int[] B) {
final int CHUNK = ... ;
for (int c = 0; c < C.length; c += CHUNK) {
fork {
for (int i = c; i < c + CHUNK; i++)
C[i] = A[i] + B[i] ; }
}
joinAll;
}
•  Comment choisir la valeur de CHUNK ?

14
Pour bien faire, il faut s’adapter à la charge ?

•  Si on a p processeurs :
•  Choisir CHUNK = n/p
•  Donc lancer p threads, peut sembler naturel.
•  Mais si certains processeurs sont déjà̀ occupes par d’autres processus
(lourds ou légers) :
•  Il y aura partage du temps, d’où inefficacité́.
•  Or le nombre de processeurs déjà̀ occupes peut varier au cours du
temps !

•  Pas facile...

15
Vers une abstrac=on de plus : les taches

•  Pour faciliter la répar==on dynamique du travail, on peut :


•  introduire une no=on de tâche,
•  poser que fork/join créent/a.endent une tâche,
•  u=liser un nombre fixe de processus légers (worker threads) pour exécuter
ces tâches,
•  ce qui suppose un algorithme efficace de répar==on des tâches entre
travailleurs (« scheduling »)

•  Plusieurs librairies implémentent ce.e idée :


•  Intel Thread Building Blocks
•  Microso• Task Parallel Library
•  Java 7 ForkJoin – c’est ce que nous allons étudier.

16
Un exemple d’u=lisa=on du modèle Fork / Join

•  Lorsque le traitement est


décomposable en sous-tâches
indépendantes
•  Existence d'algorithmes
parallèles spécifiques

•  La décomposi=on en sous-
tâches peut être :
•  sta=que : découper un tableau
en zones fixes
•  dynamique : découvrir une
arborescence de fichiers
•  A.en=on à maîtriser le volume
de tâches créées
•  récursive
Etape 1
mise en œuvre du joinAll

18
JoinAll = barrière

•  Idée : tous les processus franchissent au


même moment “la barrière”

•  ImplémentaIon :
•  Facile à implémenter par un moniteur ou
avec des sémaphores
•  Existe en Java 7, en posix

•  Principe en Java 7 :
•  Un processus crée un objet barrière
•  il donne le nombre N de processus a.endu

•  Chaque processus exécute « await » sur


l’objet barrière
•  Quand N est a.eint, la barrière s’ouvre

19
La barrière Java
public class MonRunnable implements Runnable {

private CyclicBarrier barrier;


string name = Thread.currentThread().getName();

public MonRunnable(CyclicBarrier barrier) {


this.barrier = barrier;
}

public void run() {


System.out.println(threadName + " is started");
Thread.sleep(400*new Random().nextInt(10));
System.out.println(threadName
+ " is waiting on barrier");
barrier.await();
System.out.println(threadName
+ " has crossed the barrier");
}
} 20
La barrière Java
main(String args[]) {
//creating CyclicBarrier with 3 Threads needs to call await()
CyclicBarrier cb = new CyclicBarrier(3, new Runnable(){
public void run(){
//This task will be executed once all thread reaches barrier
System.out.println("All parties are arrived at barrier");
}
});

//starting each of thread


Thread t1 = new Thread(new MonRunnable(cb), "Thread 1");
Thread t2 = new Thread(new MonRunnable(cb), "Thread 2");
Thread t3 = new Thread(new MonRunnable(cb), "Thread 3");

System.out.println("Début démarrage des threads");


t1.start(); t2.start(); t3.start();
System.out.println("Fin démarrage des threads");
}
21
La barrière Java

•  Un exemple d’exécu=on
Début démarrage des threads
Fin démarrage des threads
Thread 3 is started
Thread 1 is started
Thread 2 is started
Thread 1 is waiting on barrier
Thread 2 is waiting on barrier
Thread 3 is waiting on barrier
All parties are arrived at barrier, lets play
Thread 3 has crossed the barrier
Thread 2 has crossed the barrier
Thread 1 has crossed the barrier

•  Evidemment dans la cas de tâches cycliques, il faut réini=aliser la barrière


•  reset()
22
Mise en œuvre naïve de la barrière en Java
Class Barrier {
private final int N_THREADS;
int arrived;

public Barrier (int n) {


N_THREADS = n;
}
Sans un phénomène bien
public synchronized void aWait () {curieux appelé à Spurious
arrived++;
wakeup
if (arrived < N_THREADS) {
wait();
} else {
arrived = 0;
notifyAll();
}
}
}
23
Spurious wakeup
h6p://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

•  A thread can also wake up without being no=fied, interrupted,


or =ming out, a so-called spurious wakeup.
•  While this will rarely occur in prac=ce, applica=ons must guard
against it by tes=ng for the condi=on that should have caused
the thread to be awakened, and con=nuing to wait if the
condi=on is not sa=sfied. In other words, waits should always
occur in loops, like this one: Arrive en par=culier sur
synchronized (obj) { Linux car la mise en
while (<condition does not hold>) œuvre de la JVM repose
sur les pthread qui
obj.wait(timeout); engendre le phénomène
... // Perform action} de « Spurius wakeup »
•  For more informa=on on this topic, see Sec=on 3.2.3 in Doug
Lea's "Concurrent Programming in Java (Second
Edi=on)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's
"Effec=ve Java Programming Language Guide" (Addison-
Wesley, 2001).
24
Mise en œuvre de la barrière Java (avant le java 7)
Class Barrier {
private final int N_THREADS;
int[] counts = new int[] {0, 0};
int current = 0;

public Barrier (int n) {

}
N_THREADS = n;
Prise en compte du
Spurious wakeup
public synchronized void aWait () {
int my = current;
counts[my]++;
if (counts[my] < N_THREADS)
while (counts[my] < N_THREADS) wait();
else {
current ^= 1;
counts[current] = 0;
notifyAll();
25
}}}
Associer des tâches à des threads

26
Contrôle du parallélisme

•  Le parallélisme c’est bien mieux si on peut effecIvement calculer en


parallèle
•  Exploita=on d’une l’architecture mul=-cœur
•  Java
•  Nom du thread courant : Thread.currentThread().getName()
•  Nombre de cœur sur le processeur : Run=me.getRunCme().availableProcessors();
•  Quel est l’intérêt de lancer 10 thread si on a que 4 cœurs ?

•  Jusqu’à présent on créait des threads parallèles sans jamais se poser la


ques=on du nombre de threads qui était effec=vement exécuté en //
•  Un modèle : les « pools » de thread
•  Créer une thread coûte cher
•  Alors on recycle J

27
Pool de thread

Principe
•  On crée un ensemble de N threads
•  Généralement moins que de processeurs/coeurs existants
•  Ou au contraire légèrement plus pour prendre en compte les opéra=ons
bloquantes (entrée/sor=e, réseau)
•  On crée un ensemble de tâche
•  Le nombre dépend généralement du problème à résoudre
•  Les tâches sont successivement exécutées par les thread

Mise en œuvre en Java


à l’aide d’une nouvelle
hiérarchie : Executor

28
Classe Executors
une autre manière d’ac=ver une thread
public class MonRunnable implements Runnable {
public void run(){
System.out.println("Debut execution dans le thread "
+Thread.currentThread().getName());
//On simule un traitement long
Thread.sleep(4000);
System.out.println("Fin execution dans le thread "
+Thread.currentThread().getName());
}
}
•  Solu=on déjà connue
Thread thread = new Thread(new MonRunnable());
thread.start();
•  En u=lisant l’interface Executor
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(new MonRunnable()); 29
Classe Executors : exécu=on séquen=elle des tâches

List<Runnable> runnables = new ArrayList<Runnable>();


//création de 4 tâches
runnables.add(new MonRunnable());
runnables.add(new MonRunnable());
runnables.add(new MonRunnable());
runnables.add(new MonRunnable());

//création ‘executor’ mono-thread


ExecutorService executor =
Executors.newSingleThreadExecutor();

//exécution des tâches selon le modèle choisi


for(Runnable r : runnables){
executor.execute(r);
}
//attente de la terminaison de toutes les tâches
30
executor.shutdown();
Classe Executors : exécu=on séquen=elle des tâches

Le CPU est inac=f


Serveur pendant les E/S

A
CPU
A

B
C B

Avantage
File contenant les
• Facile à u=liser
requêtes en a.ente

31
Classe Executors : exécu=on séquen=elle des tâches

•  Evidemment les traces d’exécu=on sont :


Debut execution dans le thread pool-1-thread-1
Fin execution dans le thread pool-1-thread-1
Debut execution dans le thread pool-1-thread-1
Fin execution dans le thread pool-1-thread-1
Debut execution dans le thread pool-1-thread-1
Fin execution dans le thread pool-1-thread-1
Debut execution dans le thread pool-1-thread-1
Fin execution dans le thread pool-1-thread-1

32
Classe Executors : exécu=on parallèle des tâches

List<Runnable> runnables = new ArrayList<Runnable>();


//création de 4 tâches
runnables.add(new MonRunnable());
runnables.add(new MonRunnable());
runnables.add(new MonRunnable()); Seul
runnables.add(new MonRunnable()); changement

//création ‘executor’ pool de 2 threads


ExecutorService executor =
Executors.newFixedThreadPool(2);
//Executors.newSingleThreadExecutor();
//exécution des taches selon le modèle choisi
for(Runnable r : runnables){
executor.execute(r);
}
//attente de la terminaison de toutes les taches
executor.shutdown(); 33
Classe Executors : exécu=on parallèle des tâches
Un nombre op=mum
de thread peut être
Serveur créé et recyclé

A CPU B CPU
A

B
C

Avantage
• Moins de requête dans la file d’a.ente
• Minimize la créa=on /destruc=on de thread
Désavantage
• Nécessité de « tunning » pour =rer le meilleur par= du matériel
34
• Pas facile à me.re en œuvre avant Java 7.0
Classe Executors : exécu=on parallèle des tâches

•  Un exemple d’exécu=on des threads


Debut execution dans le thread pool-1-thread-1
Debut execution dans le thread pool-1-thread-2
Fin execution dans le thread pool-1-thread-2
Fin execution dans le thread pool-1-thread-1
Debut execution dans le thread pool-1-thread-2
Debut execution dans le thread pool-1-thread-1
Fin execution dans le thread pool-1-thread-1
Fin execution dans le thread pool-1-thread-2

35
Appel de méthode avec futur
(ou comment récupérer un résultat plus tard)

36
Un modèle : l’appel asynchrone avec avec future

•  Appel synchrone •  Appel asynchrone avec futur


appelant appelé appelant appelé
futur
client serveur client serveur

execute() submit()
new()

get()

returns() put()
returns()

•  Appel de méthode
•  Appel de procédure à distance •  Acteurs / objets ac=fs
(RPC) •  Appel aynchrone de procédure à
•  Client-serveur distance (A-RPC)
37
Runnable versus Callable<T>

Runnable Callable<T>
Introduced in Java 1.0 Introduced in Java 1.5 as part of
java.u=l.concurrent library

Runnable cannot be parametrized Callable is a parametrized type whose


type parameter indicates the return type
of its run method

Classes implemen=ng Runnable needs to Classes implemen=ng Callable needs to


implement run() method implement call() method

Runnable.run() returns no Value Callable.call() returns a value of Type T


Can not throw Checked Excep=ons Can throw Checked Excep=ons
publicinterfaceRunnable { publicinterfaceCallable<V>{
void run(); V call() throwsException;
} }

38
Classe Callable

public class MonCallable


implements Callable<Integer> {
public Integer call() {
System.out.println("Debut execution”);
//Simulation traitement long
Thread.sleep(4000);
System.out.println("Fin execution);
return new Random().nextInt(10);
}
}

39
La classe Future<T>

// création d’un “Executor”


ExecutorService executor =
Executors.newSingleThreadExecutor();
//Appel de la tache et récupération d’un Future<V>
Future<Integer> future =
executor.submit(new MonCallable());
System.out.println("Apres submit");
//On a besoin du résultat on appel : future.get()
// appel bloquant
System.out.println("Resultat=" + future.get());
System.out.println("Avant shutdown");
execute.shutdown();

40
Appel asynchrone avec futur

•  Exemple de trace
Apres submit
Debut execution
Fin execution
Resultat=6
Avant shutdown

41
Mise en oeuvre du modèle fork-join
en Java

42
Mise en oeuvre du pa.ern ‘forkjoin’ en Java

•  Exemple =ré de :
h.p://fr.openclassrooms.com/informa=que/cours/apprenez-a-
programmer-en-java/depuis-java-7-le-pa.ern-fork-join
•  Le problème
•  Parcourir tous les répertoires de mon $HOME et afficher le nombre de fichier
contenant le suffixe ‘psd’
•  Se décompose assez aisément
en sous-problèmes indépen-
dants
•  L’analyse d’un répertoire peut
être réalisé de manière récur-
sive par analyse des sous ré-
pertoire

43
Algorithme

•  Dans chaque répertoire :


•  On lance une nouvelle
analyse pour chaque sous-
répertoires

•  On compte le nombre de
fichier correspondant au
filtre

•  On a.end la terminaison
des analyses des sous
répertoires et on
addi=onne le nombre de
fichier trouvé

44
•  Approche séquen=elle •  U=lisa=on de 4 coeurs
•  Il y a 92 fichier(s) portant •  Il y a 92 fichier(s) portant
l'extension *.psd l'extension *.psd
•  Temps de traitement : 67723 •  Temps de traitement : 78679
•  Mais temps d’a.ente presque 3
fois plus cours

45
Le pa.ern forkjoin
•  ForkJoinPool : représente un <interface>
Executor
pool de thread qui reçoit la liste des
tâches à réaliser
•  invoque : lance les tâches passées
en paramètre de manière synchrones <interface>
ExecutorService
•  execute : lance les tâches passées
en paramètre de manière
asynchrones T invoke(task)
void execute(task)
•  submit : lance la tâche passée en Future<T> submit(task)
ForkJoinPool
paramètre de manière asynchrone et
renvoie un objet futur
immédiatement
•  ForkJoinTask : représente une <interface>
tâche unique Future<V>

•  invoke(): exécu=on de la tâche par


la thread courante <abstract> compute(), invoke(),
ForkJoinTask<V> fork(), join(),
•  fork(): lance une autre tâche dans isDone(), get()
le même pool que la thread courante ...

•  join(): retourne le résultat de RecursiveTask<V>


l’exécu=on de ce.e tâche
•  Un ForkJoinPool exécute des RecursiveAction
ForkJoinTasks
46
ForkJoinTask

•  RecursiveAction •  RecursiveTask<V>
•  Implémente Future<Void> •  Implémente Future<V>
•  Modélise un traitement qui ne •  Modélise un traitement récursif
renvoie pas de valeur qui renvoie une valeur
•  Peut néanmoins modifier les •  Ex : calculer la taille totale
données passées en paramètre d'une arborescence de fichiers
•  Ex : trier un tableau "in-place"

compute() est la méthode excécutée par la tâche


•  équivalent à run() pour les thread
Les tâches peuvent être appelées de manière synchrone ou
asynchrone
•  Synchrone : invoke()
•  Asynchrone : fork()
Main
// Nombre de processeurs disponibles
int processeurs =
Run=me.getRun=me().availableProcessors();

// Créa=on du pool de thread
ForkJoinPool pool =
new ForkJoinPool(processeurs);

// Créa=on de l'objet FolderScanner // Créa=on de l'objet FolderScanner
FolderScanner fs = RecursiveTask<Long> fs =
new FolderScanner(chemin, filtre); new FolderScanner(chemin, filtre);

// Analyse // Analyse
resultat = fs.sequen=alScan(); resultat = pool.invoke(fs); // exécute compute

// Un transparent qui suit décrit : // Un transparent qui suit décrit :
// sequen=alScan // compute

48
Synchrone ou Asynchrone ?
(ForkJoinPool)
Resultat = pool.invoke(fs); Resultat = pool.execute(fs);
est équivalent à : est équivalent à :

pool.invoke(fs); pool.execute(fs);

Bla bla bla


resultat = fs.join();

Bla bla bla


resultat = fs.join();

49
FolderScanner
public class FolderScanner { public class FolderScanner extends RecursiveTask<Long>
public long sequen=alScan() { public Long compute() {
// Récupéra=on de la liste des fichiers du répertoire d’analyse // Récupéra=on de la liste des fichiers du répertoire d’analyse
DirectoryStream<Path> lis=ng = Files.newDirectoryStream(path)); DirectoryStream<Path> lis=ng = Files.newDirectoryStream(path));
for (Path nom : lis=ng) { for (Path nom : lis=ng) {
if (Files.isDirectory(nom.toAbsolutePath())) { if (Files.isDirectory(nom.toAbsolutePath())) {
// S'il s'agit d'un dossier il est analysé // S'il s'agit d'un dossier il est analysé
result += new FolderScanner( subTasks.add(new FolderScanner(
nom.toAbsolutePath(), nom.toAbsolutePath(),
this.filter) this.filter)
.sequen=alScan(); .fork());
} }
}
// Récupéra=on de la liste des fichiers répondant au filtre // Récupéra=on de la liste des fichiers répondant au filtre
DirectoryStream<Path> lis=ng = DirectoryStream<Path> lis=ng =
Files.newDirectoryStream(path, this.filter)); Files.newDirectoryStream(path, this.filter));
for (Path nom : lis=ng){ for (Path nom : lis=ng) {
// Pour chaque fichier, on incrémente le compteur // Pour chaque fichier, on incrémente le compteur
result++; result++;
} }

// Récupéra=on du résultat de toutes les sous-tâches
for (ForkJoinTask<Long> subTask : subTasks)
result += subTask.join();

return result; return result; 50
Synchrone ou asynchrone ?
(ForkJoinTask/RecursiveTask/RecursiveAc=on)
if (Files.isDirectory(nom.toAbsolutePath())) { if (Files.isDirectory(nom.toAbsolutePath())) {
// S'il s'agit d'un dossier il est analysé // S'il s'agit d'un dossier il est analysé
ForkJoinTask<Long> fs = new FolderScanner( ForkJoinTask<Long> fs = new FolderScanner(
nom.toAbsolutePath(), nom.toAbsolutePath(),
this.filter) this.filter)

.invoke(); .fork();
} }
}
}


fs.fork();
fs.invoke();
Bla bla bla

fs.join();




Bla bla bla
fs.join(); 51
Asynchronisme et tâches récursives
(RecursiveTask)
// correct // Incorrect
protected Long compute() { protected Long compute() {
List<ForkJoinTask<Long>> subTasks = new ArrayList<>(); ForkJoinTask<Long> subTask;
long size = 0; long size = 0;

for(File f : root.listFiles()) { for (File f : root.listFiles()) {


if (f.isDirectory()) {
if (f.isDirectory()) {
subtask = new Task(f).fork();
subTasks.add(new Task(f).fork());
size += subtask.join();
} else {
} else {
size += f.length(); size += f.length();
} }
} }
for (ForkJoinTask<Long> subTask : subTasks) { return size;
size += subTask.join(); }
}
/* l’asynchronisme souhaité (fork)
return size;
n’est pas exploité : on a.end tout
}
de suite */
Bonnes pra=ques

•  A.en=on au coût de ges=on


•  Le bénéfice obtenu en parallélisant un traitement
doit être supérieur au coût de ges=on par le framework
•  Trouver la bonne granularité

•  A.en=on à la consomma=on mémoire


•  Grosses structures : préférer la modifica=on "in-place"
•  Découverte dynamique des sous-tâches

•  A.en=on à la complexité
•  Op=misa=on prématurée ?
•  Maintenance
Op=misa=ons

•  Framework Fork / Join


•  Threads >= Degré de parallélisme
•  Les tâches en a.ente de join() sont mises de côté
pour perme.re à d'autres tâches d'être traitées
•  Tâches
•  Eviter la synchronisa=on manuelle
•  U=liser fork() et join() uniquement
•  Op=miser la granularité à l'aide des sta=s=ques du pool
•  getQueuedSubmissionCount(), getStealCount()...
•  Possibilité d'op=misa=on dynamique
Quelques références qui m’ont aidées pour préparer ce
cours
•  API Java 7, package java.u=l.concurrent
h.p://download.java.net/jdk7/docs/api/

•  Etude de Doug Lea
h.p://gee.cs.oswego.edu/dl/papers/–.pdf

•  Des blogs/tutoriaux
•  h.p://www.oracle.com/technetwork/ar=cles/java/fork-join-422606.html
•  h.p://blog.paumard.org/2011/07/05/java-7-fork-join/
•  h.p://sdz.tdct.org/sdz/le-framework-executor.html
•  h.p://fr.openclassrooms.com/informa=que/cours/apprenez-a-programmer-
en-java/depuis-java-7-le-pa.ern-fork-join


Et si on formalisait un peu ?

56
De l’algorithme itéra=f à l’algorithme parallèle

•  Nous allons tenter de répondre à ce deux ques=ons =


•  Comment u=liser ces mécanismes pour obtenir un gain de performance ?
•  Comment prédire le gain de performance que l’on peut espérer ?
•  Exemple : addi=on point par point deux tableaux de même taille
// Semantics: C = A + B
static void add (int[] C, int[] A, int[] B) {
for (int i = 0; i < C.length; i++)
C[i] = A[i] + B[i] ;
}
•  La complexité est O(n).
•  Si on dispose de plusieurs processeurs, on peut espérer faire mieux, car les
instruc=ons C[i] = A[i] + B[i] sont indépendantes les unes des autres.
•  Parallélisa=on à outrance : 1 thread par cellule du tableau
static void add (int[] C, int[] A, int[] B) {
for (int i = 0; i < C.length; i++) {
fork { C[i] = A[i] + B[i]; }
}
joinAll ;
}
57
Un parallélisme contrôlé
•  Est-ce une bonne idée ?
•  D’un point de vue pra=que, non, ce n’est pas une bonne idée.
•  Créer un thread coute cher
•  Il faut au grand minimum plusieurs milliers d’instruc=ons pour lancer et arrêter
un processus « léger ».
•  Lancer un thread pour effectuer une tache très courte n’a aucun sens !

•  On peut lancer un nombre de threads moins important en découpant le tableau en


une série d’intervalles :
static void addChunk (int[] C, int[] A, int[] B) {
final int CHUNK = ... ;
for (int c = 0; c < C.length; c += CHUNK) {
fork {
for (int i = c; i < c + CHUNK; i++)
C[i] = A[i] + B[i] ; }
}
joinAll;
}
•  Comment choisir la valeur de CHUNK ?

58
Une version « diviser pour régner »

•  On peut souvent exprimer récursivement la décomposi=on en tâches :


static void addRec(int[] C, Int[] A, int[] B, int lo,
int hi) {
if (hi - lo <= CHUNK)
for (int i=lo; i<hi; i++)
C[i] = A[i] + B[i] ;
else {
int limit = lo+(hi-lo)/2;
fork{ addRec(C, A, B, lo, limit); }
fork{ addRec(C, A, B, limit, hi); }
joinAll ;
}
}
•  Les algorithmes « diviser pour régner » sont fondamentalement
parallèles.

59
Diagrammes de tâches : addChunk
•  Pour comprendre le comportement de ces algorithmes, il est u=le de
représenter les tâches et leurs dépendances sous forme de graphe.
•  Pour addChunk, le graphe ressemble à ceci :

•  Chaque sommet représente une suite (plus ou moins coûteuse) d’instruc=ons


exécutées séquen=ellement.

60
Diagrammes de tâches : addRec

61
Diagrammes de tâches

•  Dans le cas général, on peut


avoir un graphe orienté
acyclique, symétrique
•  La structure de ce graphe
permet de prédire quel gain de
performance on peut espérer,
au maximum, lorsque le nombre
de processeurs croît.
•  Deux quan=tés jouent un rôle
primordial :
•  le temps d’exécu=on séquen=el
•  le temps d’exécu=on parallèle

62
Travail

•  Le temps d’exécu=on séquen=el


est le cout cumulé de toutes les
taches

•  C’est le temps nécessaire pour


exécuter ces taches avec un seul
processeur

•  On le note T1.

63
Profondeur

•  Le temps d’exécu=on parallèle


est le coût cumulé des tâches
sur le chemin cri=que (chemin
“le plus lent”) car le temps
d’exécu=on est le sup des temps
de chaque processus mis en
parallèle

•  C’est le temps nécessaire pour


exécuter ces tâches avec une
infinité́ de processeurs

•  On le note T∞.
Parallélisme

•  Même si l’on dispose d’une infinité́ de processeurs, le gain de


performance est donc au maximum
•  T1 / T∞ = temps séquen=el / temps parallèle
•  Ce.e quan=té́ est appelée « maximum speedup » ou parallélisme

•  On peut évaluer (asympto=quement, dans le pire cas) le travail et la


profondeur à l’aide des techniques d’analyse que vous connaissez.

•  On applique les règles :


•  T1(I1 || I2) = T1(I1) + T1(I2)
•  T∞(I1 || I2) = max(T∞(I1), T∞(I2))
•  où « I1 || I2 » est une abrévia=on pour « fork I1; fork I2; joinAll ».

65
Analyse asympto=que : exemple 1
static void addRec(int[] C, int[] A, int[] B, int lo, int hi) {
if (hi - lo <= CHUNK)
for (int i = lo;i < hi;i++) C[i] = A[i] + B[i] ;
else {
int limit = lo + (hi - lo) / 2;
fork { addRec(C, A, B, lo, limit); }
fork { addRec(C, A, B, limit, hi); }
joinAll;
}
}

•  Nous avons les équa=ons de récurrence suivantes, pour n assez grand :


•  T1(n) = 2T1(n/2) + O(1)
•  T∞(n) = T∞(n/2) + O(1)
•  D’où... ?

66
Analyse asympto=que : exemple 1

•  Nous avons les équa=ons de récurrence suivantes, pour n assez grand :


•  T1(n) = 2T1(n/2) + O(1) d’où T1(n) = Θ(n)
•  T∞(n) = T∞(n/2) + O(1) d’où T∞(n) = Θ(log n)

•  Le parallélisme est élevé : T1/T∞ = Θ( n/log(n) ).


•  C’est bon signe.

67
Analyse asympto=que : exemple 2
// SemanCcs : B[lo..hi] = sort(A[lo..hi])
// T[lo..hi) can be used as temporary storage staIc void mergeSortRec
staIc void mergeSortRec (int[] B, int[] A, int lo, int hi, int[] T) {
if (hi - lo == 0) return ;
if (hi - lo == 1) B[lo] = A[lo] ;
else {
int limit = lo + (hi - lo) / 2;
spawn { mergeSortRec(T, A, lo, limit, B); }
spawn { mergeSortRec(T, A, limit, hi, B); }
sync ;
merge(B, T, lo, limit, hi); // auxiliary funcCon
}
}

À nouveau :
•  T1(n) = 2T1(n/2) + f(n)
•  T∞(n) = T∞(n/2) + f(n)
•  f(n) est le coût de merge...

68
Analyse asympto=que : exemple 2

•  La fonc=on auxiliaire merge effectue la fusion de deux tableaux triés :


// SemanCcs : B[lo..hi) = sort(T[lo..hi))
// Requires : T[lo..limit) and T[limit..hi) are sorted
staIc void merge (int[] B, int[] T, int lo, int limit, int hi) {
int dst = lo;
int src1 = lo ;
int src2 = limit ;
while (src1 < limit && src2 < hi)
B[dst++] = T[src1]<T[src2] ? T[src1++] : T[src2++] ;
while (src1 < limit)
B[dst++] = T[src1++] ;
while (src2 < hi)
B[dst++] = T[src2++] ;
}
•  Sa complexité est O(n).

69
Analyse asympto=que : exemple 2

•  Nous avons donc les équa=ons de récurrence :


•  T1(n) =2T1(n/2)+O(n) d’où work(n)=Θ(n log n)
•  T∞(n) = T∞(n/2) + O(n) d’où T∞(n) = Θ(n)
•  Le parallélisme est faible : T1 / T∞ = Θ( n log n / n ) = Θ(log n).

70
Analyse asympto=que : exemple 2

•  En fait, le cout parallèle est mauvais car une por=on non-négligeable du


calcul se fait en séquen=el, c’est la loi d’Amdahl.
•  Dans notre cas par=culier : le speed up ne peut dépasser 1/B (B por=on
séquen=elle)

•  Pour entre complet, la loi de Gustafson montre qu’on a aussi intérêt à


augmenter le nombre de données sur lequel faire fonc=onner un code
parallèle

•  Pour augmenté le parallélisme et améliorer le temps d’exécu=on, on va
probablement devoir paralléliser aussi la fonc=on auxiliaire merge.

71
Que retenir ?

•  Nous avons :
•  présenté les threads et les opéra=ons élémentaires «fork» et «join»
•  suggèré une no=on plus abstraite de tache dotée d’opéra=ons analogues
•  regardé la mise en œuvre de ce pa.ern de programma=on en Java

•  suggèré que, pour espérer obtenir un gain de performance, il faut contrôler


la granularité́ des taches et les dépendances entre taches.

72
Un modèle proche mais complémentaire
Map Reduce

Sera présenté en détail dans certains parcours


de 5ème année

73
Map Reduce

•  Un paradigme / modèle de programma=on pour le développement


logiciel qui permet
•  Une parallélisa=on automa=que
•  De l’équilibrage de charge
•  Des op=misa=ons sur les transferts disques et réseaux
•  De la tolérance aux pannes
•  Trouve ses origines dans les méthodes map et reduce/fold des langages
fonc=onnels.
•  Basé sur trois opéra=ons principales
•  Map : une fonc=on appliquée à chaque par=e des données d’entrée pour
générer une série de couple <clé , valeur>
•  Shuffle : regroupement des couples <clé ; valeur> par clé dis=ncte
•  Reduce : une autre fonc=on appliquée à chacun des groupes ainsi généré
(donc par clé dis=ncte).

74
Map Reduce

Le code d’un programme est donc


1.  Lecture des données
2.  Map
•  A chaque élément du flux repéré par une clé K1, Map associe un liste de
couple <clé, valeur>
•  Map: <K1, V1> à list (K2, V2)
3.  Shuffle/Sort : regrouper les sor=es et les trier selon les clés K2
4.  Reduce
•  Exécuté une fois la phase Map terminée
•  Agréga=on en liste de toutes les valeurs intermédiaires associées à une clé K2
•  Agréga=on est une opéra=on associa=ve
•  Reduce: <K2, list(V2) > à list (K3, V3) – bien souvent : K2 = K3
5.  Ecrire les données

75
Pourquoi Map / Reduce

•  Traiter des volumes de données sans cesse grandissant nécessite une


plus forte puissance de calcul
•  La cadence des processeurs stagne depuis le début des années 2000
•  L’évolu=on se tourne vers la mul=plica=on des cœurs et l’exploita=on du
parallélisme
•  Le développement des programmes parallèle devient un besoin croissant
et répandu
•  C’était auparavant excep=onnel
•  Formaliser une méthodologie commune :
•  Pour le développement de programme
•  Pour leur mise en œuvre de manière aisée sur divers types d’architecture
•  Rendre les exécu=ons adaptables en fonc=on du volume de données à traiter

76
Map / reduce

•  Méthodologie qui a été formalisée pour la première fois par Google en


2004
•  MapReduce: Simplified Data Processing on Large Clusters
•  h.ps://sta=v/googleusercontent.com/media/reserach.google.com/en//
archive/mapreduce-osdi04/pdf
•  Intérêt de l’approche
•  Perme.re de paralléliser une exécu=on sur autant de machine que
nécéssaire
•  Traiter des volumes de données aussi large que nécessaire
•  Mise en œuvre
•  En développant deux fonc=ons simples : map et reduce
•  Quelque soit le nombre de machine et le volume de données
•  Evolu=on portée par de grands groupes privées (Google, Yahoo) mais
aussi par des fonda=ons liées au logiciel libre ou Open Source (Apache)
•  Hadoop : projet apache, principale implémenta=on du framework

77
Map / Reduce

•  A u=liser si
•  On souhaite travailler sur de grands volumes de données (de l’ordre du GB /
TB)
•  On souhaite résoudre un problème complexe, très consommateur de
ressources processeurs
•  Et plus généralement, lorsqu’un problème doit être parallélisé car son
exécu=on sur une machine unique n’est pas réaliste
•  A ne pas u=liser si
•  Le volume de données est limité et l’exécu=on sur une machine unique est
viable
•  Les données d’entrées ne sont pas segmentable / découpables efficacement
•  Il faut que les tâches à réaliser soient indépendantes les unes des autres

78
Map / Reduce – un exemple

•  Compter les occurrences d’un mot dans un ensemble de fichiers


•  Le « hello, World » de Map Reduce
•  Données ini=ales :
•  Le contenu de l’intégralité de la bibliothèque na=onale
•  Objec=fs
•  Connaître les mots les plus populaires et les mots les moins populaires/

•  Le problème porte sur un très grands volume de données


•  Il est parallélisable : on peut compter les mots de chaque livre, de chaque
page puis faire la synthèse

79
Map / Reduce – un exemple

•  Données ini=ales
Celui qui croyait au ciel
Celui qui n’y croyait pas
[…]
Fou qui fait le délicat
Fou qui songe à ses querelles
•  L. Aragon, La rose et le Réséda, 1943
•  On commence par découper les données d’entrée, de telle sorte que la
fradmenta=on résulte en des blocs de données suscep=bles d’être traités
indépendamment sans influence sur le résultat final
•  Exemple : chaque ligne du texte d’entrée devient un « bloc » de données qu’il
faut « ne.oyer » (suppression des majuscules, des accents, de la
ponctua=on, etc.)

80
Map / Reduce – un exemple

•  Données
Celui qui croyait au ciel
Celui qui n’y croyait pas
Fou qui fait le délicat
Fou qui songe à ses querelles

•  on considère conceptuellement ces 4 blocs de données d'entrée comme 4


couples <clef , valeur> pour lesquels la clef est nulle et la valeur est
cons=tuée de la ligne de texte
•  L’ opéra=on map va produire une série de couples < clef , valeur > – et
ces couples seront lors de l'opéra=on suivante (shuffle) regroupés par clef
dis=ncte.
•  L’opéra=on reduce aura quand à elle pour but de réduire ces groupes
triés par clef dis=ncte; le choix de la clef à u=liser est donc crucial.

81
Map / Reduce – un exemple

•  Ici, on génère comme clef au sein de l'opéra=on map le mot lui-même; et


comme valeur correspondante, la constante « 1 » (quelque soit le mot
rencontré).
•  Le rôle de la fonc=on map sera donc de parcourir la ligne d'entrée, et
pour chaque mot dis=nct de générer un couple
•  < clef , valeur> = < mot ,1>.
Celui qui croyait au ciel <celui, 1><qui, 1><croyait, 1><au, 1>…

Celui qui n’y croyait pas <celui, 1><qui, 1><ny, 1><croyait, 1>…

Fou qui fait le délicat <fou, 1><qui, 1><fait, 1><le, 1>…


Fou qui songe à ses
<fou, 1><qui, 1><songe, 1><a, 1>…
querelles

82
Map / Reduce – un exemple

•  Aprés exécu=on de map et de shuffle, les couples <clef, valeur> ainsi


générés seront regroupés (par le framework d'exécu=on du programme
sur le cluster, par exemple Hadoop) par clef dis=ncte:
<celui, 1><celui, 1>
<celui, 1><qui, 1><croyait, 1><au, 1>… <qui, 1><qui, 1><qui, 1><qui, 1>
<celui, 1><qui, 1><ny, 1><croyait, 1>… <croyait, 1><croyait, 1>
<fou, 1><qui, 1><fait, 1><le, 1>… <au, 1>
<fou, 1><qui, 1><songe, 1><a, 1>… <ny, 1>

<ciel, 1>

•  Chacun de ces groupes dis=ncts sera passé en entrée de la fonc=on


reduce.

83
Map / Reduce – un exemple

•  Quand à l’opéra=on reduce, elle va recevoir un groupe de couples


•  <clef , valeur> en entrée : ceux qui correspondent à une des clefs
dis=nctes.
•  Son rôle va simplement être de conserver la clef unique, d'addi=onner les
valeurs de tous les couples <clef , valeur> reçus en entrée, et générer un
unique couple <clef, valeur> en sor=e, composé de la clef unique et du
total obtenu.

<celui, 1><celui, 1> <celui, 2>

<qui, 1><qui, 1><qui, 1> <qui, 1> <qui, 4>

<croyait, 1><croyait, 1> <croyait, 2>

84
Map / Reduce – un exemple

•  Au terme de l'exécu=on du programme map reduce dans son ensemble,


on ob=endra une série de couples <clef , valeur> : un pour chacun des
mots uniques présents dans le texte. Pour chacune de ces clefs, on aura
le nombre d'occurences du mot dans le texte.
•  Comme le programme a été implémenté à par=r de la méthodologie
map/reduce, il est parallélisable; et pourrait si on dispose d'assez de
machines s'exécuter en quelques secondes même sur un texte d'entrée
très volumineux.
qui: 4
celui: 2
croyait: 2
fou: 2
au: 1
ciel: 1
ny: 1
pas: 1
fait: 1

85
Schéma général

(source: documentaCon Hadoop)

86
Un exemple moins trivial

87
Le graphe social

•  On dispose de la base de données d'un réseau social contenant plusieurs


millions d'u=lisateurs. Pour chacun d'entre eux, on a une liste d'autres
u=lisateurs: les amis de l'u=lisateur courant sur le réseau.
•  On cherche à générer, pour chaque couple d'u=lisateurs dis=ncts, la liste
des amis qu'ils ont en commun.
•  On ne peut pas effectuer ce.e opéra=on par le biais d'une requête sur la
base de données rela=onnelle sans un impact immense sur le serveur du
réseau social, poten=ellement bloquant pour la base, et donc pour le site
lui-même.
•  Par conséquent, on va créer une tâche map/reduce pour régler le
problème, et l'exécuter à intervalles réguliers sur un export de la base de
données rela=onnelle; on imagine ensuite que les résultats seront ré-
importés dans une table dédiée à cet usage.

88
Graphe social

•  Ici, les données d’entrée sont sous la forme U=lisateur è amis


•  A è B, C, D
•  B è A, C, D, E
•  C è A, B, D, E
•  D è A, B, C, E
•  E è B, C, D
•  Les données sont fragmentables (par « ligne » i.e. par u=lisateur)
•  Choix de la clé
•  On va choisir une chaîne de caractère, sous la forme : « A-B »
•  Ce.e clef, par exemple, désignera « les amis en commun des u=lisateurs A et B ».

•  Le ra=onnel derrière ce choix vient du fait que la clef représente l'élément autour
duquel sont regroupés les données lors de l'opéra=on intermédiaire (shuffle); elle
correspond au point commun entre les couples (clef; valeur) qui va perme.re de
réduire les données lors de l'opéra=on reduce.
•  Par ailleurs, on fera également en sorte que la clef soit toujours triée par ordre
alphabé=que (clef « B-A » sera exprimée sous la forme « A-B »).

89
Graphe social

•  L'opéra=on map est au coeur de la résolu=on du problème. Elle va


prendre la liste des amis fournie en entrée, et va générer toutes les clefs
dis=nctes possibles à par=r de ce.e liste. Pour chacune de ces clef, elle va
générer un couple (clef;valeur) dont la valeur sera la liste d'amis, telle
quelle.
•  Ce traitement peut paraître contre-intui=f, mais il va à terme nous
perme.re d'obtenir, pour chaque clef dis=ncte, deux couples
(clef;valeur): les deux listes d'amis de chacun des u=lisateurs qui
composent la clef.
•  Par exemple,
•  pour la première ligne : A è B, C, D
•  On ob=endra les couples (clef;valeur) :
•  (A-B; B C D)
•  (A-C; B C D)
•  (A-D ; B C D)

90
Graphe social

•  Le pseudo-code de l'opéra=on map

UTILISATEUR = [PREMIERE PARTIE DE LA LIGNE]


POUR AMI dans [RESTE DE LA LIGNE], FAIRE:
SI UTILISATEUR < AMI:
CLEF = UTILISATEUR+"-"+AMI
SINON:
CLEF = AMI+"-"+UTILISATEUR
GENERER COUPLE (CLEF; [RESTE DE LA LIGNE])

91
Graphe Social
•  Pour la seconde ligne : B è A, C, D, E
•  On ob=endra ainsi
•  (A-B, A C D E), (B-C, A, C, D, E), (B-D, A, C, D, E), (B-E, A, C, D, E)
•  Etc sur les 5 lignes d’entrée
•  Une fois l'opéra=on MAP effectuée, l'étape de shuffle va récupérer les
couples (clef;valeur) de tous les fragments et les grouper par clef dis=ncte. Le
résultat sur la base de nos données d'entrée
•  Pour la clef "A-B": valeurs "A C D E" et "B C D"
•  Pour la clef "A-C": valeurs "A B D E" et "B C D"
•  Pour la clef "A-D": valeurs "A B C E" et "B C D"
•  Pour la clef "B-C": valeurs "A B D E" et "A C D E"
•  Pour la clef "B-D": valeurs "A B C E" et "A C D E"
•  Pour la clef "B-E": valeurs "A C D E" et "B C D"
•  Pour la clef "C-D": valeurs "A B C E" et "A B D E"
•  Pour la clef "C-E": valeurs "A B D E" et "B C D"
•  Pour la clef "D-E": valeurs "A B C E" et "B C D »
•  On ob=ent bien, pour chaque clef « USER1-USER2 », deux listes d'amis: les
amis de USER1 et ceux de USER2

92
Graphe Social

•  L’opéra=on reduce se révèle par conséquent évidente: elle va recevoir


chacun de ces groupes de deux couples (clef;valeur), parcourir les deux
listes d'amis pour générer une liste d'amis communs, et renvoyer un seul
couple (clef;valeur), dont la clef sera iden=que à la clef dis=ncte
correspondant au groupe d'entrée, et la valeur sera ce.e liste d'amis
communs.
•  Pseudo code
LISTE_AMIS_COMMUNS=[] // Liste vide au départ.
SI LONGUEUR(VALEURS)!=2, ALORS: // Ne devrait pas se produire.
RENVOYER ERREUR
SINON:
POUR AMI DANS VALEURS[0], FAIRE:
SI AMI EGALEMENT PRESENT DANS VALEURS[1], ALORS:
// Présent dans les deux listes d'amis, on l'ajoute.
LISTE_AMIS_COMMUNS+=AMI
RENVOYER LISTE_AMIS_COMMUNS

93
Graphe social

•  Après exécu=on de l'opéra=on REDUCE pour les valeurs de chaque clef


unique, on ob=endra donc, pour une clef « A-B », les u=lisateurs qui
apparaissent dans la liste des amis de A et dans la liste des amis de B.
Autrement dit, on ob=endra la liste des amis en commun des u=lisateurs A et
B.
•  Le résultat:
•  "A-B": "C, D"
•  "A-C": "B, D"
•  "A-D": "B, C"
•  "B-C": "A, D, E"
•  "B-D": "A, C, E"
•  "B-E": "C, D"
•  "C-D": "A, B, E"
•  "C-E": "B, D"
•  "D-E": "B, C"
•  On sait ainsi que A et B ont pour amis communs les u=lisateurs C et D, ou
encore que B et C ont pour amis communs les u=lisateurs A, D et E.

94
Graphe social

•  Les deux opéra=ons map et reduce sont sommes toutes rela=vement


simples, mais combinées à un framework d'exécu=on map reduce, elles
effectuent un travail complexe.
•  Par ailleurs, la tâche est parallélisable: on peut l'exécuter aussi vite que
nécessaire du moment qu'on a assez de machines au sein du cluster
d'exécu=on.
•  Dans le cadre de l'exemple, on imagine que ce type de tâche serait
exécuté toutes les heures sur un cluster Hadoop dédié à cet effet;
perme.ant toutes les heures de me.re à jour la table rela=onnelle «
classique » contenant les listes d'amis en communs avec un coût
inexistant pour la base de données rela=onnelle.

95
Autres exemples
•  Une entreprise dispose d'un compte twi.er pour son service aprés vente,
recevant plusieurs dizaines de milliers de tweets par jour. Elle cherche à
déterminer le taux de sa=sfac=on de ses clients à par=r du compte twi.er.
Chaque heure, les tweets reçus sont exportés au sein d'un fichier texte.
•  clef: un descripteur de sen=ment client (« sa=sfait », « insa=sfait » ou « a.ente »,
« inconcluant »).
•  map: génère un couple (clef;valeur) par sen=ment client détecté (mot
correspondant à une liste prédéfinie).
•  Si deux sen=ments contradictoires détectés: renvoyer inconcluant.
•  On renvoie un couple (clef;valeur) pour chaque fragment des données d'entrée: chaque
tweet
•  reduce: addi=onne les valeurs associées à la clef unique; renvoie le total pour
valeur (iden=que au reducer du compteur d'occurences de mots).
•  Exemple
"@acme Votre service client est nul" ("insa=sfait";1)
"@acme 30min d'a.ente... très insa=sfait" ("insa=sfait";1) ("insa=sfait";2)
"Très sa=sfait par un produit super !! @acme" ("sa=sfait";1) ("sa=sfait";1)
"merci d'avoir RT @acme" ("inconcluant";1) ("inconcluant";2)
"@acme produit déjà cassé, super insa=sfait ! » ("inconcluant";1)

•  Conclusion: lors de la dernière heure, 33% de tweets exprimant de la


sa=sfac=on.
•  En important, heure par heure, les données au sein d'une base de données
rela=onnelle, on pourra obtenir en permanence des sta=s=ques sur la 96
sa=sfac=on à par=r de twi.er.
Conclusion

•  ApplicaIons courantes de map/reduce et Hadoop:


•  Analyse de logs et données en général, validaIon de données,
recoupements, filtrage, etc. => traitements sur des volumes de données
massifs.
•  ExécuIon de tâches intensives en CPU de manière distribuée (simulaIons
scienIfiques, encodage vidéo, etc.).
•  … et bien souvent les deux: tâches intensives en CPU sur des volumes
de données massifs (entraînement de modèles en machine learning,
etc.)
•  Plus complexe, voire contre-producIf à appliquer sur tout problème où
la fragmentaIon des données d'entrée pose problème (en fait sur tout
problème où la stratégie du diviser pour régner n'est pas viable).

97
98
Exemple (Mise en oeuvre)

•  L’u=lisateur programme les fonc=ons Map / Reduce


•  En pseudo Java
Map(String input_key, String input_value) {
foreach word w in input_value do {
EmitIntermediate (w, 1);
}
Reduce (String key, Iterator<Integer> intermediate_value)
{
int result =0;
foreach v in intermediate_value do {
result += v;
}
•  Le framework fourni les fonc=ons SPLIT / SORT mais aussi la ges=on du
flux de données

99
ForkJoin versus MapReduce

ForkJoin MapReduce
Single machine Cluster machine
Takes advantage of mul=ple cores Take advantages of cluster machine.
Massively scalable.
ForkJoin recursively par==ons a task into Does only one big split, with no
several subtasks, on a single machine communica=on between the parts un=l
the reduce step.
Starts quickly and scales well for small MapReduce takes tens of seconds to start
inputs (<5MB), but it cannot process up, but scales well for much larger inputs
larger inputs due to the size restric=ons (>100MB) on a compute cluster.
of shared-memory, single node
architectures.
No fault tolerance Generally, fault tolerant implementa=on,
No distribu=on transparent task distribu=on
Exist in Java since Java 7 Exist since 2004
Popularized by google
Open source implementa=on: hadoop
(h.p://hadoop.apache.org/) 100
Q&A

h.p://www.i3s.unice.fr/~riveill

101
Livre u=lisé pour ce chapitre

•  Doug Lea
•  Professor
•  State University of New York at
Oswego

h.p://g.oswego.edu

102

Vous aimerez peut-être aussi