MapReduce sur une machine
BGD701 — Télécom Paris
multiprocessing, et mesurer le gain10 000 fichiers texte : compter les occurrences de chaque mot.
Google, 2004 : imposer un modèle très contraint pour automatiser tout le reste. C’est l’ancêtre direct d’Hadoop.
grep + awk = le map. sort = le shuffle. uniq -c = le reduce.
La recette classique, tr -c '[:alpha:]' '\n', détruit les accents sous Linux : tr y travaille octet par octet, et é occupe deux octets.
« L’été, déjà là » →
l t d j l
grep -oE + awk : correct sur les machines de l’école et sur Mac.
MapReduce, c’est map | sort | reduce à l’échelle d’un centre de données.
("chat", 1) : la clé dit de quoi on parle, la valeur ce qu’on en saityield : un générateur qui livre les couples un par un, sans tout garder en mémoire.
| Phase | Contenu |
|---|---|
Map (f1 : le chat dort) |
(le,1) (chat,1) (dort,1) |
Map (f2 : le chien dort) |
(le,1) (chien,1) (dort,1) |
| Shuffle | le:[1,1] chat:[1] dort:[1,1] chien:[1] |
| Reduce | le:2 chat:1 dort:2 chien:1 |
Les tâches map sont indépendantes : n’importe où, dans n’importe quel ordre, relançables en cas de panne.
def mapreduce(fichiers, fonction_map, fonction_reduce):
# ---- Phase MAP ----
intermediaires = []
for nom in fichiers:
with open(nom, encoding="utf-8", errors="ignore") as f:
contenu = f.read()
intermediaires.extend(fonction_map(nom, contenu))
# ---- Phase SHUFFLE : regrouper par cle ----
groupes = {}
for cle, valeur in intermediaires:
groupes.setdefault(cle, []).append(valeur)
# ---- Phase REDUCE ----
resultat = {}
for cle, valeurs in groupes.items():
resultat[cle] = fonction_reduce(cle, valeurs)
return resultat| Problème | map émet | reduce fait |
|---|---|---|
| Comptage de mots | (mot, 1) |
la somme |
| Longueurs de mots | (len(mot), 1) |
la somme |
| Index inversé | (mot, fichier) |
la liste des fichiers |
| Maximum par catégorie | (cat, valeur) |
le maximum |
| Doublons | (ligne, 1) |
garde si somme |
Avec cent machines, les couples sont éparpillés. Il faut décider à l’avance quel réducteur traite quelle clé, sans se concerter.
| Mot | le |
chat |
chien |
dort |
|---|---|---|---|---|
| Réducteur, sur un Mac | 1 | 1 | 0 | 1 |
Réducteur, sur tp-1a201-18 |
1 | 1 | 0 | 1 |
hashlib, jamais hash() : salé au hasard à chaque démarrage.
fichiers intermédiaires : le shuffle est la phase la plus coûteuse.
Au lieu d’écrire mille fois ("le", 1), écrire une fois ("le", 1000).
| Fichier | Sans combiner | Avec combiner | Facteur |
|---|---|---|---|
| 1 Mo | 2,22 Mo | 0,31 Mo | ≈ 7 |
| 8 Mo | 17,61 Mo | 0,55 Mo | ≈ 32 |
Un couple par mot distinct. Plus les données sont grosses, plus le gain est grand.
Le combiner réduit en deux temps. Il faut que ce soit sans effet :
La moyenne ne l’est pas
Moyenne de et de : 4. Moyenne des deux moyennes : 6. On transporte (somme, effectif), on divise à la fin.
multiprocessing.Pool(p) : ouvriers. pool.map(f, liste) = un [f(x) for x in liste] en parallèle, qui attend tout le monde.
Deux pool.map successifs : aucun réducteur ne commence avant la fin de tous les maps. C’est la barrière.
if __name__ == "__main__": obligatoire. Sur Mac (spawn), sans lui : 958 RuntimeError en 20 s, jamais fini. À l’école (fork, Python 3.11) : ça marche… jusqu’au jour où l’on change de machine__main__ : une variable globale calculée là lui est inconnue/tmp est partagé entre utilisateurs : un dossier à votre nom, avec getpass.getuser().
8 fichiers de 1 Mo, , tp-1a201-18
| 1 | 2 | 3 | 4 | 5 | 6 | 8 | |
|---|---|---|---|---|---|---|---|
| total (s) | 4,75 | 2,41 | 1,84 | 1,20 | 1,21 | 1,20 | 0,67 |
| accélération | 1,0 | 2,0 | 2,6 | 4,0 | 3,9 | 4,0 | 7,1 |
4, 5 et 6 processus : le même temps. Pourquoi ?
Une tâche map = 0,54 s. Le processus le plus chargé fixe le temps.
| Répartition des 8 tâches | Vagues | Map | |
|---|---|---|---|
| 4 | 2 + 2 + 2 + 2 | 2 | 1,08 s |
| 5 | 2 + 2 + 2 + 1 + 1 | 2 | 1,09 s |
| 6 | 2 + 2 + 1 + 1 + 1 + 1 | 2 | 1,08 s |
| 8 | 1 + 1 + … + 1 | 1 | 0,55 s |
L’accélération est limitée par le nombre de tâches. Pour 12 cœurs, il faut au moins 12 tâches.
C’est la part séquentielle du calcul. En séance 7 : la loi d’Amdahl.
assert.map | sort | reduce à grande échelle ; le sort, c’est le shufflemap et reduce, le système fait le restehash(cle) % R : déterministe, sans concertationreduce est associative