Site de Jean-Michel RICHER

Maître de Conférences en Informatique à l'Université d'Angers

Ce site est en cours de reconstruction certains liens peuvent ne pas fonctionner ou certaines images peuvent ne pas s'afficher.


stacks

8. Easy MPI

8.1. Objectif

Créer deux classes afin de représenter une variable d'un type donné ou un tableau de manière à pouvoir envoyer ou recevoir leurs contenus en utilisant MPI.

Nous avons besoin d'une fonction qui permet d'obtenir le type MPI équivalent à un type C.

Afficher le code    ens/m2/paracpu/td8/easy_mpi_get_type.cpp
  1. #include <iostream>
  2. #include <sstream>
  3. #include <stdexcept>
  4. #include <cstdint>
  5. #include <vector>
  6. #include <unistd.h>
  7. #include <cassert>
  8. #include <mpi.h>
  9.  
  10. /**
  11.  * Determine type of data T and convert it into MPI::Datatype.
  12.  * This function needs to be extended with other types.
  13.  */
  14. template <class T>
  15. MPI::Datatype mpi_get_type()
  16. {
  17.     if (typeid(T) == typeid(char))
  18.     {
  19.         return MPI::CHAR;
  20.     }
  21.     else if (typeid(T) == typeid(int8_t))
  22.     {
  23.         return MPI::CHAR;
  24.     }
  25.     else if (typeid(T) == typeid(uint8_t))
  26.     {
  27.         return MPI::CHAR;
  28.     }
  29.     else if (typeid(T) == typeid(int))
  30.     {
  31.         return MPI::INT;
  32.     }
  33.     else if (typeid(T) == typeid(float))
  34.     {
  35.         return MPI::FLOAT;
  36.     }
  37.     else if (typeid(T) == typeid(double))
  38.     {
  39.         return MPI::DOUBLE;
  40.     }
  41.     else if (typeid(T) == typeid(size_t))
  42.     {
  43.         return MPI::UNSIGNED_LONG;
  44.     }
  45.  
  46.     ostringstream oss;
  47.     oss << "unknown type: " << typeid(T).name() << endl;
  48.     throw std::runtime_error(oss.str());
  49.  
  50.     return MPI::INT;
  51. }
  52.  

8.2. MPIVariable

La première classe MPIVariable est une classe générique qui contient une valeur (champ _value).

Afficher le code    ens/m2/paracpu/td8/easy_mpi_variable.cpp
  1. /**
  2.  * Classe MPIVariable qui permet d'envoyer ou de recevoir
  3.  * la valeur d'une variable
  4.  */
  5. template <class T>
  6. class MPIVariable
  7. {
  8. public:
  9.     T _value;
  10.  
  11.     /**
  12.      * Constructeur
  13.      */
  14.     MPIVariable(T value) { /* écrire le code */ }
  15.  
  16.     /**
  17.      * Envoyer à un autre processus
  18.      * @param to_id identifiant du processus qui reçoit la variable
  19.      */
  20.     void send_to(int to_id)
  21.     {
  22.         // écrire le code
  23.     }
  24.  
  25.     /**
  26.      * Recevoir d'un autre processus le contenu d'une variable
  27.      * @param from_id identifiant du processus qui envoie la variable
  28.      */
  29.     void recv_from(int from_id)
  30.     {
  31.         // écrire le code
  32.     }
  33.  
  34.     /**
  35.      * Retourner la valeur de la variable
  36.      */
  37.     T &operator()()
  38.     {
  39.         // écrire le code
  40.     }
  41. };
  42.  

Exercice 8.1

Ecrire un progamme qui permet d'échanger le contenu d'une MPIVariable entre deux processus.

Afficher le code    ens/m2/paracpu/td8/easy_mpi_test_variable.cpp
  1. int main(int argc, char *argv[])
  2. {
  3.     int max_cpus, cpu_id;
  4.  
  5.     // initilisation
  6.     MPI::Init(argc, argv);
  7.  
  8.     // nombre de processus / thread / programmes
  9.     max_cpus = MPI::COMM_WORLD.Get_size();
  10.     // identifiant de processus / thread / programme
  11.     cpu_id = MPI::COMM_WORLD.Get_rank();
  12.  
  13.     // gestion des erreurs éventuelles
  14.     MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
  15.  
  16.     if (cpu_id == 0)
  17.     {
  18.         // la valeur de la variable est définie sur le processus
  19.         // maître
  20.         MPIVariable<int> var(1234);
  21.         var.send_to(1);
  22.     }
  23.     else
  24.     {
  25.         // l'esclave déclare une variable de même type
  26.         // et la reçoit du processus maître
  27.         MPIVariable<int> var(0);
  28.         var.recv_from(0);
  29.  
  30.         cout << "variable=" << var() << endl;
  31.     }
  32.  
  33.     MPI::Finalize();
  34.  
  35.     return EXIT_SUCCESS;
  36. }
  37.  

8.3. MPIArray

La seconde classe MPIArray est également une classe générique.

Afficher le code    ens/m2/paracpu/td8/easy_mpi_array.cpp
  1. /**
  2.  * classe MPIArray définie par sa taille et ses données
  3.  * qui sont allouée sous forme d'un tableau C
  4.  */
  5. template <class T>
  6. class MPIArray
  7. {
  8. public:
  9.     // taille du tableau en nombre d'éléments
  10.     int _size;
  11.     // pointeur sur les éléments du tableau
  12.     T *_elements;
  13.  
  14.     /**
  15.      * Constructeur par défaut qui définit un tableau
  16.      * de taille 0
  17.      */
  18.     MPIArray()
  19.     {
  20.         // écrire le code
  21.     }
  22.  
  23.     /**
  24.      * Constructeur avec taille du tableau en paramètre
  25.      */
  26.     MPIArray(T size) : _size(size)
  27.     {
  28.         // écrire le code
  29.     }
  30.  
  31.     /**
  32.      * Destructeur qui libère la mémoire
  33.      */
  34.     ~MPIArray()
  35.     {
  36.         // écrire le code
  37.     }
  38.  
  39.     /**
  40.      * Affichage des éléments qui composent le tableau
  41.      */
  42.     friend ostream &operator<<(ostream &out, MPIArray<T> &obj)
  43.     {
  44.         out << "[";
  45.         for (int i = 0; i < obj._size; ++i)
  46.         {
  47.             out << obj._elements[i] << " ";
  48.         }
  49.         out << "]";
  50.         return out;
  51.     }
  52.  
  53.     /**
  54.      * Redimensionnement du tableau : on crée un tableau de taille new_size
  55.      * et on garde les min(_size, new_size) éléments
  56.      */
  57.     void resize(int new_size)
  58.     {
  59.         // écrire le code
  60.     }
  61.  
  62.     /**
  63.      * Envoi du tableau (taille + éléments) au processus d'identifiant to_id
  64.      */
  65.     void send_to(int to_id)
  66.     {
  67.         // écrire le code
  68.     }
  69.  
  70.     /**
  71.      * Réception du tableau (taille + éléments) envoyé par le processus
  72.      * d'identifiant from_id
  73.      */
  74.     void recv_from(int from_id)
  75.     {
  76.         // écrire le code
  77.     }
  78.  
  79.     /**
  80.      * Retourne la taille du tableau
  81.      */
  82.     int size()
  83.     {
  84.         return _size;
  85.     }
  86.  
  87.     /**
  88.      * Retourne le pointeur vers les éléments
  89.      */
  90.     T *operator()()
  91.     {
  92.         return _elements;
  93.     }
  94.  
  95.     /**
  96.      * Retourne le n-ième élément
  97.      */
  98.     T &operator[](int n)
  99.     {
  100.         // écrire le code
  101.     }
  102.  
  103.     /**
  104.      * Remplissage du tableau avec des valeurs croissantes
  105.      */
  106.     void iota()
  107.     {
  108.         for (int i = 0; i < _size; ++i)
  109.         {
  110.             _elements[i] = i;
  111.         }
  112.     }
  113.  
  114.     /**
  115.      * Réalise le découpage du tableau en nbr_cpus parties équitables
  116.      * 1) envoi aux nbr_cpus-1 escalves de leur partie du tableau
  117.      * 2) redimensionnement du tableau pour le master
  118.      */
  119.     void scatter(int cpu_id, int nbr_cpus)
  120.     {
  121.         // écrire le code
  122.     }
  123.  
  124.     /**
  125.      * Création d'un tableau à partir du tableau du master et des
  126.      * tableaux des nbr_cpus-1 esclaves
  127.      */
  128.     void gather(int cpu_id, int nbr_cpus)
  129.     {
  130.         // écrire le code
  131.     }
  132. };
  133.  

Exercice 8.2

Ecrire notamment les méthodes scatter et gather et les utiliser :

  • le maître crée un tableau de taille 1027
  • et le remplit avec la méthode iota()
  • le processus maître envoie chaque partie aux esclaves (au nombre de 3) et garde une partie pour lui grâce à la méthode scatter()
  • chaque processus (maître + esclaves) multiplie les valeurs de son tableau par 2
  • chaque processus renvoie son tableau au processus maître qui en fait l'amalgame avec son propre tableau en utilisant la méthode gather
Afficher le code    ens/m2/paracpu/td8/easy_mpi_test_array.cpp
  1. int main(int argc, char *argv[])
  2. {
  3.     int max_cpus, cpu_id;
  4.  
  5.     // initialisation des ressources
  6.     MPI::Init(argc, argv);
  7.  
  8.     int SIZE = 100;
  9.  
  10.     if (argc > 1)
  11.     {
  12.         SIZE = std::stoi(argv[1]);
  13.     }
  14.  
  15.     // get number of programs running
  16.     max_cpus = MPI::COMM_WORLD.Get_size();
  17.     // get program identifier
  18.     cpu_id = MPI::COMM_WORLD.Get_rank();
  19.  
  20.     MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
  21.  
  22.     if (cpu_id == 0)
  23.     {
  24.         MPIArray<int> array(SIZE);
  25.         array.iota();
  26.  
  27.         array.scatter(cpu_id, max_cpus);
  28.  
  29.         array.gather(cpu_id, max_cpus);
  30.  
  31.         cout << "MASTER GATHER =" << array << endl;
  32.     }
  33.     else
  34.     {
  35.  
  36.         MPIArray<int> array;
  37.         array.scatter(cpu_id, max_cpus);
  38.  
  39.         for (int i = 0; i < array.size(); ++i)
  40.         {
  41.             array[i] = array[i] * (cpu_id + 1);
  42.         }
  43.  
  44.         array.gather(cpu_id, max_cpus);
  45.     }
  46.  
  47.     MPI::Finalize();
  48.  
  49.     return EXIT_SUCCESS;
  50. }
  51.