Paralèlisme : cours

Liste des travaux pratiques / dirigés

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.

  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).

  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.

  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.

  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
  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.