Académique Documents
Professionnel Documents
Culture Documents
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
• 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 → ∞
5
Comment augmenter le parallélisme
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
Head Tail
Object lock
a b c d
Head Tail
a b c d
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
• 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).
13
Une version moins agressive
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
16
Un exemple d’u=lisa=on du modèle Fork / Join
• 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
• 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
19
La barrière Java
public class MonRunnable implements Runnable {
• 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
}
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
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
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
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
32
Classe Executors : exécu=on parallèle des tâches
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
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
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
38
Classe Callable
39
La classe Future<T>
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
• 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>
• 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"
48
Synchrone ou Asynchrone ?
(ForkJoinPool)
Resultat = pool.invoke(fs); Resultat = pool.execute(fs);
est équivalent à : est équivalent à :
pool.invoke(fs); pool.execute(fs);
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;
• A.en=on à la complexité
• Op=misa=on prématurée ?
• Maintenance
Op=misa=ons
Et si on formalisait un peu ?
56
De l’algorithme itéra=f à l’algorithme parallèle
58
Une version « diviser pour régner »
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 :
60
Diagrammes de tâches : addRec
61
Diagrammes de tâches
62
Travail
• On le note T1.
63
Profondeur
• On le note T∞.
Parallélisme
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;
}
}
66
Analyse asympto=que : exemple 1
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
69
Analyse asympto=que : exemple 2
70
Analyse asympto=que : exemple 2
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
72
Un modèle proche mais complémentaire
Map Reduce
73
Map Reduce
74
Map Reduce
75
Pourquoi Map / Reduce
76
Map / reduce
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
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
81
Map / Reduce – un exemple
Celui qui n’y croyait pas <celui, 1><qui, 1><ny, 1><croyait, 1>…
82
Map / Reduce – un exemple
<ciel, 1>
83
Map / Reduce – un exemple
84
Map / Reduce – un exemple
86
Un exemple moins trivial
87
Le graphe social
88
Graphe social
• 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
90
Graphe social
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
93
Graphe social
94
Graphe social
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)
97
98
Exemple (Mise en oeuvre)
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