Processus
• Le nombre est déterminé au début de l’exécution
• Demeure constant en cours d’exécution
• Tous les processus exécutent le même programme
• Chaque processus possède son propre ID
• Le passage de messages sert à la communication et à
la synchronisation• Même un message vide a une signification
Particularités du modèlepar passage de messages
• Portabilité– Fonctionne aussi bien sur des multi-ordinateurs que sur des
multiprocesseurs
• Facilité de déboguage– Aucune variable partagée
• Sur les multi-ordinateurs le temps de communication est élevé– On doit minimiser la communication– Moins de fautes de cache que sur les multiprocesseurs
Exemple: Satisfaisabilité de circuits
• Le problème de la satisfaction de circuits est NP-
complet
• Aucun algorithme polynomial connu
• Force brute: On regarde toutes les solutions
possibles: Inefficace
• 16 bits en entrée 216 = 65,536 combinaisons à tester
Allocation cyclique des entrées aux processus
• On suppose qu’il y a p processus• On distribue les entrées aux processus de façon
cyclique• Exemple: 5 processus et 12 entrées:
– P0: 0, 5, 10
– P1: 1, 6, 11
– P2: 2, 7
– P3: 3, 8
– P4: 4, 9
Quiz
Supposons qu’il y ait p processus et n entrées possibles et que l’allocation soit cyclique.
1. Quel nombre maximum d’entrées va-t-on allouer à un processus?
2. Quel nombre minimum d’entrées va-t-on allouer à un processus?
3. Combien de processus se voient allouer le nombre maximum d’entrées?
Stratégie générale
• Le programme considère les 65,536 entrées possibles
sur 16 bits
• Les entrées sont allouées de façon cyclique
• Chaque processus examine chacune des entrées qui
lui sont allouées
• Chaque entrée satisfaisant le circuit est affichée.
Variables
int main (int argc, char *argv[]) { int i; int id; /* Rang du processus */ int p; /* Nombre de processus */ void check_circuit (int, int);
argc et argv sont nécessaire pour initialiser MPI Chaque processus possède sa propre copie des variables
puisqu’il exécute sa propre copie du programme.
Initialisation de MPI
• Utiliser avant toute autre fonction MPI• Pas nécessairement la première instruction• Permet d’effectuer les réglages nécessaires
à l’utilisation de MPI• Analogie: Constructeur en POO
MPI_Init (&argc, &argv);
Communicators
• Communicator: Groupe de processus– Objet opaque offrant l’environnement nécessaire
au passage de messages entre les processus• MPI_COMM_WORLD
– Communicator par défaut– Contient tous les processus
• Il est possible de créer d’autres communicators
Déterminer le nombre de processus
• Premier paramètre: nom du communicator• Valeur de retour: Code d’erreur• Nombre de processus: mis à l’adresse &p
int MPI_Comm_size (MPI_COMM_WORLD, &p);
Déterminer le rang d’un processus
• Premier paramètre: Nom du communicator• Le rang (0, 1, …, p-1) est retourné dans le second
paramètre
int MPI_Comm_rank (MPI_COMM_WORLD, &id);
Allocation cyclique
for (i = id; i < 65536; i += p) check_circuit (id, i);
• Le parallélisme se situe à l’extérieur de la fonction check_circuit
• check_circuit est une fonction séquentielle ordinaire
Terminer l’utilisation de MPI
• Dernier appel MPI• Permet au système de libérer les ressources
MPI• Analogie: Destructeur en POO
MPI_Finalize();
#include <mpi.h>#include <stdio.h>
int main (int argc, char *argv[]) { int i; int id; int p; void check_circuit (int, int);
MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &id); MPI_Comm_size (MPI_COMM_WORLD, &p);
for (i = id; i < 65536; i += p) check_circuit (id, i);
printf ("Process %d is done\n", id); fflush (stdout); MPI_Finalize(); return 0;}
/* Retourne 1 si le i-ième bit de ‘n’ est 1; 0 sinon */#define EXTRACT_BIT(n,i) ((n&(1<<i))?1:0)
void check_circuit (int id, int z) { int v[16]; /* Each element is a bit of z */ int i;
for (i = 0; i < 16; i++) v[i] = EXTRACT_BIT(z,i);
if ((v[0] || v[1]) && (!v[1] || !v[3]) && (v[2] || v[3]) && (!v[3] || !v[4]) && (v[4] || !v[5]) && (v[5] || !v[6]) && (v[5] || v[6]) && (v[6] || !v[15]) && (v[7] || !v[8]) && (!v[7] || !v[13]) && (v[8] || v[9]) && (v[8] || !v[9]) && (!v[9] || !v[10]) && (v[9] || v[11]) && (v[10] || v[11]) && (v[12] || v[13]) && (v[13] || !v[14]) && (v[14] || v[15])) { printf ("%d) %d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d\n", id, v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8],v[9], v[10],v[11],v[12],v[13],v[14],v[15]); fflush (stdout); }}
Avant de compiler
• ssh –l mpi** linuxmpi1.uqac.ca
• lamboot: À utiliser par chaque usager. Cela sert à démarrer le démon qui gère LAM/MPI.
• lamhalt: Pour arrêter LAM/MPI• lamnodes: Pour connaître les processeurs disponibles• laminfo: Information sur la configuration de LAM/MPI• scp: Pour copier un fichier d’une machine à une autre
Compiler un programme MPI
• mpicc: pour compiler des fichiers C ou C++ utilisant MPI
• Équivalent à:
gcc -I/usr/local/include -pthread -L/usr/local/lib -llammpio -llamf77mpi -lmpi -llam -lutil –ldl
• Faire mpicc –showme pour voir l’équivalence
mpicc –o f f.c
Exécuter un programme MPI
• mpirun –np <N> <exec> [<arg1> …]• mpirun –c <N> <exec> [<arg1> …]
– -np <N> nombre de processus– -c <N> nombre de processus– <exec> exécutable– [<arg1> …] arguments de la ligne de commande
Permettre la connection à distance
• MPI doit pouvoir démarrer des processus sur d’autres processeurs sans fournir de mot de passe
• Chaque processeur dans le groupe doit inclure la liste des autres processeurs dans le fichier .rhosts
• Par exemple sur linuxmpi1.uqac.ca (192.168.237.64):192.168.237.65192.168.237.66192.168.237.67
Exécution sur 1 CPU
% mpirun -np 1 sat0) 10101111100110010) 01101111100110010) 11101111100110010) 10101111110110010) 01101111110110010) 11101111110110010) 10101111101110010) 01101111101110010) 1110111110111001Process 0 is done
Exécution sur 2 CPU
% mpirun -np 2 sat0) 01101111100110010) 01101111110110010) 01101111101110011) 10101111100110011) 11101111100110011) 10101111110110011) 11101111110110011) 10101111101110011) 1110111110111001Process 0 is doneProcess 1 is done
Exécution sur 3 CPU
% mpirun -np 3 sat0) 01101111100110010) 11101111110110012) 10101111100110011) 11101111100110011) 10101111110110011) 01101111101110010) 10101111101110012) 01101111110110012) 1110111110111001Process 1 is doneProcess 2 is doneProcess 0 is done
Améliorer le programme
• On veut trouver le nombre total de solutions• On ajoute une réduction au programme• La réduction est une opération collective qui
implique une communication entre tous les processus
Modifications
• check_circuit– Retourne 1 si le circuit est satisfaisable avec
l’entrée donnée– Retourne 0 sinon
• Chaque processus compte localement le nombre d’entrées satisfaisant le circuit
• La réduction est effectuée sur l’ensemble des résultats.
/* * Circuit Satisfiability, Version 2 * * This enhanced version of the program prints the * total number of solutions. */
#include "mpi.h"#include <stdio.h>
int main (int argc, char *argv[]) { int count; /* Solutions found by this proc */ int global_count; /* Total number of solutions */ int i; int id; /* Process rank */ int p; /* Number of processes */ int check_circuit (int, int);
MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &id); MPI_Comm_size (MPI_COMM_WORLD, &p);
count = 0; for (i = id; i < 65536; i += p) count += check_circuit (id, i);
MPI_Reduce (&count, &global_count, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); printf ("Process %d is done\n", id); fflush (stdout); MPI_Finalize(); if (!id) printf ("There are %d different solutions\n", global_count); return 0;} /* FIN du main */
/* Return 1 if 'i'th bit of 'n' is 1; 0 otherwise */#define EXTRACT_BIT(n,i) ((n&(1<<i))?1:0)
int check_circuit (int id, int z) { int v[16]; /* Each element is a bit of z */ int i;
for (i = 0; i < 16; i++) v[i] = EXTRACT_BIT(z,i); if ((v[0] || v[1]) && (!v[1] || !v[3]) && (v[2] || v[3]) && (!v[3] || !v[4]) && (v[4] || !v[5]) && (v[5] || !v[6]) && (v[5] || v[6]) && (v[6] || !v[15]) && (v[7] || !v[8]) && (!v[7] || !v[13]) && (v[8] || v[9]) && (v[8] || !v[9]) && (!v[9] || !v[10]) && (v[9] || v[11]) && (v[10] || v[11]) && (v[12] || v[13]) && (v[13] || !v[14]) && (v[14] || v[15])) { printf ("%d) %d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d\n", id, v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8],v[9], v[10],v[11],v[12],v[13],v[14],v[15]); fflush (stdout); return 1; } else return 0;}
Prototype de MPI_Reduce()int MPI_Reduce ( void *operand, /* Adresse du tableau d’entrée*/ void *result, /* Adresse du tableau des résultat */ int nbre, /* nombre de réductions à effectuer*/ MPI_Datatype type, /* type des éléments*/ MPI_Op operator, /* Opérateur de réduction*/ int root, /* Rang du processus qui reçoit le résultat*/ MPI_Comm comm /* communicator */
)
MPI_Datatype
• MPI_CHAR• MPI_DOUBLE• MPI_FLOAT• MPI_INT• MPI_LONG• MPI_LONG_DOUBLE• MPI_SHORT• MPI_UNSIGNED_CHAR• MPI_UNSIGNED• MPI_UNSIGNED_LONG• MPI_UNSIGNED_SHORT
MPI_Op
• MPI_BAND• MPI_BOR• MPI_BXOR• MPI_LAND• MPI_LOR• MPI_LXOR• MPI_MAX• MPI_MAXLOC• MPI_MIN• MPI_MINLOC• MPI_PROD• MPI_SUM
Exécution du second programme
% mpirun -np 3 seq20) 01101111100110010) 11101111110110011) 11101111100110011) 10101111110110012) 10101111100110012) 01101111110110012) 11101111101110011) 01101111101110010) 1010111110111001Process 1 is doneProcess 2 is doneProcess 0 is doneThere are 9 different solutions
Analyse de la performance
• MPI_Barrier barrière de synchronization• MPI_Wtick temps en secondes entre 2 tics d’horloge• MPI_Wtime temps en secondes depuis le dernier appel