• Aucun résultat trouvé

Sous-ensemble des primitives et du langage

4.5 Optimisation par raffinement de partitions

5.1.2 Sous-ensemble des primitives et du langage

À cette date, le standard MPI, présentement à sa 3ème version majeure4, distingue environ 300 primitives. Parmi celles-ci, on retrouve certaines permettant de factoriser la syntaxe (e.g.

MPI_ReduceAll(...)est équivalent à MPI_Reduce(...) ; MPI_Bcast(...)), d’autres de déclarer des types de données portables, ou encore de gérer des entrées/sorties, . . .

Dans le cadre de notre prototype, nous avons ciblé un sous-ensemble de MPI et du langage C qui nous a semblé, dans un premier temps, suffisamment expressif pour décrire des programmes intéressants. Ainsi, les primitives traitées par notre prototype ressemblent fortement aux primitives présentes dans le langage donné par la Figure 4.1 du chapitre précédent. Dans un soucis d’établir une preuve de concept, nous nous sommes limités à des domaines abstraits relativement simples. De ce fait, nous simplifions le langage C en, notamment, n’autorisant pas d’allocation dynamique, d’appels de fonctions et de types de données non-scalaires. Nous reviendrons sur ces choix par la suite en proposant des pistes permettant de lever ces contraintes.

Le standard MPI décrit de manière détaillée le comportement attendu des différentes primitives mais n’établit pas de sémantique formelle. Les programmes MPI peuvent alors avoir des comportements indéfinis selon les implantations de la bibliothèque employées. Certaines implantations de MPI, lors d’un envoi de message synchrone, s’autorisent de ne pas attendre la confirmation de réception d’un message

pour reprendre leur exécution. Les travaux présentés par Guodong Li et al. [LDGK08] proposent la formalisation d’un sous-ensemble des primitives permettant de désambiguïser celles-ci.

Nous présentons ci-dessous les différentes primitives que notre prototype, en l’état, traite. La séman-tique que nous associons à chacune d’entre elles est celle que nous décrivons dans le chapitre précédent.

Communications point-à-point

Les deux premières primitives de communication que nous traitons sont :

int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)

int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

La sémantique de ces deux primitives est similaire à celle des instructions send et receive présen-tées dans le chapitre précédent. Ces communications sont synchrones et on les suppose bloquantes tant que la valeur n’est pas reçue par le processus récepteur.

Toutes les primitives MPI retournent un entier correspondant à un code d’erreur, 0 si tout s’est déroulé correctement.

— buf est l’adresse pointant vers la donnée à envoyer ; — count est le nombre d’éléments que l’on souhaite envoyer ; — datatype est le type des éléments traités ;

— dest et source sont les identifiants respectifs du destinataire et du récepteur. Il est possible de donner MPI_ANY_SRC en identifiant de réception pour recevoir de n’importe quel émetteur ; — tag est un marqueur spécifiant le type d’un message. Pour effectuer une communication, les

deux instructions doivent porter le même marqueur ;

— comm est un contexte de communication (par défaut : MPI_COMM_WORLD) ;

— status est la structure qui sera remplie lorsque que la communication sera effectuée et qui contiendra les informations liées à la communication.

Communications collectives

Les deux communications collectives que nous traitons sont, comme dans notre langage défini dans le chapitre précédent, la communication globale et la réduction globale.

int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int root, int tag, MPI_Comm comm)

La sémantique est, ici encore, semblable à celle précédemment donnée. Les arguments sont les mêmes que pour la communication point-à-point outre que :

— root est l’identifiant du processus envoyant sa valeur à tous les autres ;

— buf est l’adresse contenant la valeur que le processus d’id root va envoyer mais également l’adresse où sera stockée la valeur reçue.

5.1. INTRODUCTION À MPI 75

int MPI_Reduce(const void *sendbuf, void *recvbuf, int count,

MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)

En MPI, l’opération de réduction ne respecte pas d’ordre dans les réductions effectuées. Ainsi, MPI suppose l’opérateur associatif et ne contredit donc pas notre sémantique attribuant, elle, un ordre aux réductions.

— root est l’identifiant du processus qui reçoit la valeur réduite ;

— sendbuf est l’adresse contenant la valeur que tous les processus vont envoyer pour effectuer la réduction ;

— recvbuf est l’adresse à laquelle sera stockée le résultat du calcul. Cette valeur ne sera accessible que par le processus d’identifiant root.

Création dynamique de processus

En MPI, il existe une primitive permettant de créer dynamiquement des processus. Cependant, le comportement de cette primitive « haut-niveau » est différent de la sémantique que nous avons donnée : cette primitive crée des processus fils associés à l’appelant. Par la suite, ces processus fils ne seront capables de ne communiquer qu’avec leur parent.

MPI_Comm_spawn(char *command, char *argv[], int maxprocs, MPI_info info, int root, MPI_Comm comm,

MPI_Comm *intercomm, int array_of_errcodes[])

— command est la chaîne correspondant au fichier exécutable que les processus-fils vont lancer ; — le tableau argv contient les arguments avec lequel les processus-fils vont démarrer leurs

pro-grammes ;

— maxprocs est le nombre de processus que le processus parent va tenter de créer (selon le nombre de machines disponibles)

— info est un ensemble de paires clé/valeur (string/string) permettant de configurer l’exécution des processus-fils ;

— root est l’identifiant du processus dont le contexte va être utilisé pour évaluer les précédents arguments ;

— comm communicateur interne contenant le groupe des processus-fils ;

— intercomm, après l’appel, contiendra le communicateur externe qui sera associé à tous les pro-cessus et dans lequel ils seront contraints de communiquer ;

— array_of_errcodes contiendra l’ensemble des codes d’erreurs retournés par les processus. Pour manipuler cette primitive, il est nécessaire de comprendre le concept des communicators. Les communicateurs sont des partitionnements d’espace de communications. Ils permettent de définir une topologie des différents processus. En utilisant les communicateurs, il est donc possible d’effectuer une communication multi-point avec un sous-ensemble de processus en instanciant un nouveau communi-cateur et en remplaçant la constante MPI_COMM_WORLD vue jusqu’à présent.

Cette primitive est complexe à utiliser et ne s’applique qu’à certains cas particuliers. Par ailleurs, le standard MPI déconseille l’utilisation de la création dynamique en MPI pour des raisons de perfor-mances.

Pour ces raisons, nous avons fait le choix de nous abstraire de ces notions haut-niveau et de définir une nouvelle primitive de création dynamique afin de pouvoir valider nos résultats théoriques :

void MPI_Create(int *new_id)

où new_id contiendra l’identifiant du nouveau processus créé. La sémantique de cette primitive est équivalente à celle donnée au cours du chapitre précédent. Bien que n’appartenant pas à l’ensemble des primitives officielles, nous avons fait le choix d’ajouter le préfixe ”MPI_” pour la distinguer des primitives C.