Un petit cours de comm ?
Adrien Merlini & Serge Guelton
https://www.google.com/about/datacenters
June 1993: Rpeak 131 GFlops, 1,024 Cores, USA June 2003: RPeak 40,960 GFlops, 5,120 Cores, Japan June 2013: RPeak 54,902 GFlops, 3,120,000 Cores, China June 2020: RPeak 513,584 GFlops, 7,299,072 Cores, Japan
https://www.top500.org/lists/green500/
June 2013: 3208.8 MFlops/watts June 2020: 21.108 GFlops/watts, 393 in TOP500
https://setiathome.berkeley.edu/
> analyse observationnelle cherchant à détecter de la vie intelligente non terrestre Depuis 1999 !
Objectif officieux: prouver la viabilité d'une grille distribuée à base de machines sur étagères
successeur : https://boinc.berkeley.edu/
https://www.mpi-forum.org/docs/
> Message Passing Interface
Tellement important que les constructeurs fournissent leur propre versions (e.g. Intel, IBM…)
https://docs.celeryproject.org/
> Distributed Task Queue
> workload manager
https://www.gnu.org/software/make/
> build automation
Surcoûts possibles :
Qui s'ajoutent au temps d'exécution de la tâche…
Aux débuts de l'informatique, les processeurs étaient lents :
→ on optimise le nombre d'instruction, pas les transferts mémoires
au 21ème siècle, les processeurs sont rapides :
→ on optimise les accès mémoires
Mais tout dépend du système ! MPI ≠ OpenMP
Que se passe-t-il quand un processeur arm32 bit envoie un tableau de int à un processeur amd64 ?
Écueils possibles :
Création et fermeture de contexte :
Les autres appels MPI ne sont valides qu'entre ces deux appels
int main(int argc, char **argv) { MPI_Init(&argc, &argv); sleep(random()); MPI_Finalize(); return 0; }
Q: Quand se termine le programme principal ?
communicateur = ensemble de processus + contexte de comm prédéfini: MPI_Comm MPI_COMM_WORLD
int main(int argc, char **argv) { MPI_Init(&argc, &argv); int my_rank, num_procs; MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); printf("%d/%d\n", my_rank, num_procs); MPI_Finalize(); return 0; }
Q: Quel stdout est utilisé ?
int MPI_Send(void *buf, // données à envoyer int count, // nombre d'élément données MPI_Datatype datatype, // type d'un élément int dest, // étiquette du destinataire int tag, // identifiant du message MPI_Comm comm, // communicateur choisi );
int MPI_Recv(void *buf, // adresse de réception int count, // nombre max. d'éléments MPI_Datatype datatype, // type d'un élément int source, // rang de la source int tag, // étiquette du message attendu MPI_Comm comm, // communicateur choisi MPI_Status *status // descripteur de la com. );
int main(int argc, char **argv) { MPI_Init(&argc, &argv); int my_rank, num_procs, from_rank; MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Send(&my_rank, 1, MPI_INT, (my_rank + 1) % num_procs, 42, MPI_COMM_WORLD); MPI_Recv(&from_rank, 1, MPI_INT, (my_rank - 1) % num_procs, 42, MPI_COMM_WORLD, MPI_STATUS_IGNORE); assert(from_rank == (my_rank - 1) % num_procs); MPI_Finalize(); return 0; }
struct MPIStatus{ int MPI_SOURCE ; // rang de la source, cf. MPI_ANY_SOURCE int MPI_TAG ; // ́étiquette du message recu, cf. MPI_ANY_TAG int MPI_ERROR ; // code d'erreur }; int MPI_Get_count(MPI_Status *status, // statut à inspecter MPI_Datatype datatype, // type des éléments transférés int *count // nombre d'éléments reçus );
char buffer[512]; MPI_Status status; MPI_Recv(buffer, 512, MPI_CHAR, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status); int buffer_count; MPI_Get_count(&status, MPI_CHAR, &buffer_count); printf("recieved message from %d with tag %d\n", status.MPI_SOURCE, status.MPI_TAG); for(int i = 0; i < buffer_count; ++i) stuff(buffer[i]);
int MPI_Probe(int source, // rang de la source int tag, // étiquette du message attendu MPI_Comm comm, // communicateur choisi MPI_Status *status // descripteur de la com. );
Bloquant, pratique pour dimensionner un tampon avant un MPI_Recv.
int MPI_Isend(..., // mêmes arguments que MPI_Send MPI_Request* req // requète interrogeable ); int MPI_Wait(MPI_Request* req, // requète à interroger MPI_Status* status // status de la requète ); int MPI_Test(MPI_Request *req, // requète à interroger int *flag, // positionné à 0 si envoi non terminé MPI_Status *status // statut à maj );
int MPI_Bcast(void *buffer, // cf. MPI_Send / MPI_Recv int count, // cf. MPI_Send / MPI_Recv MPI_Datatype datatype, // cf. MPI_Send / MPI_Recv int root, // rang de l'envoyeur MPI_Comm comm // cf. MPI_Send );
[!] les nœuds de rang ≠ root reçoivent les données à l'issue de cet appel
SCATTER GATHER A: +-- 0 0 -- + B: +-- 1 1 -- + C: [0,1,2,3] > -| | - > [0, 1, 2, 3] D: +-- 2 2 -- + E: +-- 3 3 -- +
int MPI_Scatter( // description des données envoyées aux autres nœuds const void *sendbuf, int sendcount, MPI_Datatype sendtype, // description desdonnées reçues par chaque autre nœud void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, // rang de l'envoyeur MPI_Comm comm // … ); [!] recvcount * comm_size = sendcount
// idem int MPI_Gather( const void * sendbuf, int sendcount, MPI_Datatype sendtype, void * recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm) [!] recvcount = sendcount * comm_size
+/- : performance, #communication, portabilité ?
API pour accéder de manières distribuée aux données