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.
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <cstdint>
#include <vector>
#include <unistd.h>
#include <cassert>
#include <mpi.h>
/**
* Determine type of data T and convert it into MPI::Datatype.
* This function needs to be extended with other types.
*/
template <class T>
MPI::Datatype mpi_get_type()
{
if (typeid(T) == typeid(char))
{
return MPI::CHAR;
}
else if (typeid(T) == typeid(int8_t))
{
return MPI::CHAR;
}
else if (typeid(T) == typeid(uint8_t))
{
return MPI::CHAR;
}
else if (typeid(T) == typeid(int))
{
return MPI::INT;
}
else if (typeid(T) == typeid(float))
{
return MPI::FLOAT;
}
else if (typeid(T) == typeid(double))
{
return MPI::DOUBLE;
}
else if (typeid(T) == typeid(size_t))
{
return MPI::UNSIGNED_LONG;
}
ostringstream oss;
oss << "unknown type: " << typeid(T).name() << endl;
throw std::runtime_error(oss.str());
return MPI::INT;
}
8.2. MPIVariable
La première classe MPIVariable est une classe générique qui contient une valeur (champ _value).
/**
* Classe MPIVariable qui permet d'envoyer ou de recevoir
* la valeur d'une variable
*/
template <class T>
class MPIVariable
{
public:
T _value;
/**
* Constructeur
*/
MPIVariable(T value) { /* écrire le code */ }
/**
* Envoyer à un autre processus
* @param to_id identifiant du processus qui reçoit la variable
*/
void send_to(int to_id)
{
// écrire le code
}
/**
* Recevoir d'un autre processus le contenu d'une variable
* @param from_id identifiant du processus qui envoie la variable
*/
void recv_from(int from_id)
{
// écrire le code
}
/**
* Retourner la valeur de la variable
*/
T &operator()()
{
// écrire le code
}
};
Exercice 8.1
Ecrire un progamme qui permet d'échanger le contenu d'une MPIVariable entre deux processus.
int main(int argc, char *argv[])
{
int max_cpus, cpu_id;
// initilisation
MPI::Init(argc, argv);
// nombre de processus / thread / programmes
max_cpus = MPI::COMM_WORLD.Get_size();
// identifiant de processus / thread / programme
cpu_id = MPI::COMM_WORLD.Get_rank();
// gestion des erreurs éventuelles
MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
if (cpu_id == 0)
{
// la valeur de la variable est définie sur le processus
// maître
MPIVariable<int> var(1234);
var.send_to(1);
}
else
{
// l'esclave déclare une variable de même type
// et la reçoit du processus maître
MPIVariable<int> var(0);
var.recv_from(0);
cout << "variable=" << var() << endl;
}
MPI::Finalize();
return EXIT_SUCCESS;
}
8.3. MPIArray
La seconde classe MPIArray est également une classe générique.
/**
* classe MPIArray définie par sa taille et ses données
* qui sont allouée sous forme d'un tableau C
*/
template <class T>
class MPIArray
{
public:
// taille du tableau en nombre d'éléments
int _size;
// pointeur sur les éléments du tableau
T *_elements;
/**
* Constructeur par défaut qui définit un tableau
* de taille 0
*/
MPIArray()
{
// écrire le code
}
/**
* Constructeur avec taille du tableau en paramètre
*/
MPIArray(T size) : _size(size)
{
// écrire le code
}
/**
* Destructeur qui libère la mémoire
*/
~MPIArray()
{
// écrire le code
}
/**
* Affichage des éléments qui composent le tableau
*/
friend ostream &operator<<(ostream &out, MPIArray<T> &obj)
{
out << "[";
for (int i = 0; i < obj._size; ++i)
{
out << obj._elements[i] << " ";
}
out << "]";
return out;
}
/**
* Redimensionnement du tableau : on crée un tableau de taille new_size
* et on garde les min(_size, new_size) éléments
*/
void resize(int new_size)
{
// écrire le code
}
/**
* Envoi du tableau (taille + éléments) au processus d'identifiant to_id
*/
void send_to(int to_id)
{
// écrire le code
}
/**
* Réception du tableau (taille + éléments) envoyé par le processus
* d'identifiant from_id
*/
void recv_from(int from_id)
{
// écrire le code
}
/**
* Retourne la taille du tableau
*/
int size()
{
return _size;
}
/**
* Retourne le pointeur vers les éléments
*/
T *operator()()
{
return _elements;
}
/**
* Retourne le n-ième élément
*/
T &operator[](int n)
{
// écrire le code
}
/**
* Remplissage du tableau avec des valeurs croissantes
*/
void iota()
{
for (int i = 0; i < _size; ++i)
{
_elements[i] = i;
}
}
/**
* Réalise le découpage du tableau en nbr_cpus parties équitables
* 1) envoi aux nbr_cpus-1 escalves de leur partie du tableau
* 2) redimensionnement du tableau pour le master
*/
void scatter(int cpu_id, int nbr_cpus)
{
// écrire le code
}
/**
* Création d'un tableau à partir du tableau du master et des
* tableaux des nbr_cpus-1 esclaves
*/
void gather(int cpu_id, int nbr_cpus)
{
// écrire le code
}
};
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
int main(int argc, char *argv[])
{
int max_cpus, cpu_id;
// initialisation des ressources
MPI::Init(argc, argv);
int SIZE = 100;
if (argc > 1)
{
SIZE = std::stoi(argv[1]);
}
// get number of programs running
max_cpus = MPI::COMM_WORLD.Get_size();
// get program identifier
cpu_id = MPI::COMM_WORLD.Get_rank();
MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
if (cpu_id == 0)
{
MPIArray<int> array(SIZE);
array.iota();
array.scatter(cpu_id, max_cpus);
array.gather(cpu_id, max_cpus);
cout << "MASTER GATHER =" << array << endl;
}
else
{
MPIArray<int> array;
array.scatter(cpu_id, max_cpus);
for (int i = 0; i < array.size(); ++i)
{
array[i] = array[i] * (cpu_id + 1);
}
array.gather(cpu_id, max_cpus);
}
MPI::Finalize();
return EXIT_SUCCESS;
}