Je voulais utiliser boost::mpi communicateurs dans ma classe, car je veux que ma classe s'occupe de tous les appels MPI. J'ai utilisé ce style pour en faire des membres statiques de ma classe.

// works.cpp
// mpic++ -o works works.cpp -lboost_mpi
#include <boost/mpi.hpp>
#include <iostream>

class Example {
  static boost::mpi::environment env;
  static boost::mpi::communicator world; 
 public:
  Example() {
    std::cout << world.rank() << std::endl;
  }
};

boost::mpi::environment Example::env;
boost::mpi::communicator Example::world;

int main() {
  auto e = Example();
}

Cela fonctionne très bien, par exemple, mpirun -n 4 ./works imprime les nombres 0 à 3, comme prévu. Plus tard, j'ai voulu créer un modèle pour ma classe. Au début, je me demandais comment initialiser mes variables de membre statiques, mais lisez cette réponse, qui suggérait que tout allait bien

// fails.cpp
// mpic++ -o fails fails.cpp -lboost_mpi
#include <boost/mpi.hpp>
#include <iostream>

template<typename T>
class Example {
  static boost::mpi::environment env;
  static boost::mpi::communicator world; 
 public:
  Example() {
    std::cout << world.rank() << std::endl;
  }
};

template <typename T>
boost::mpi::environment Example<T>::env;

template <typename T>
boost::mpi::communicator Example<T>::world;

int main() {
  auto e = Example<double>();
}

Cependant, cela me donne en fait

$ mpirun -n 4 ./fails
*** The MPI_Comm_rank() function was called before MPI_INIT was invoked.
*** This is disallowed by the MPI standard.
*** Your MPI job will now abort.
...

Quelque chose ne va pas ici, mais quoi? et pourquoi? Je pense que je ne comprends pas bien certaines choses ici.

2
innisfree 7 mai 2020 à 11:31

3 réponses

Meilleure réponse

Une raison particulière pour laquelle vous avez besoin d'une instance boost::mpi::environment dans vos cours? C'est juste un horrible wrapper OO autour de la nature actuellement singleton de MPI - vous ne pouvez initialiser MPI qu'une seule fois en appelant MPI_Init() et le finaliser une seule fois par la suite en appelant MPI_Finalize() (au moins, jusqu'à ce que les sessions MPI font dans les futures versions de la norme). Si vous essayez de faire quelque chose en rapport avec MPI avant MPI_Init(), vous obtenez une erreur (à l'exception d'une poignée d'appels de requête d'informations qui fonctionnent). Si vous essayez de faire quelque chose en rapport avec MPI après MPI_Finalize(), vous obtenez une erreur. Si un seul rang se termine sans appeler MPI_Finalize() après avoir appelé MPI_Init(), le travail MPI entier se bloque généralement car le lanceur suppose que quelque chose de mal s'est produit et tue le reste des rangs.

Le seul but de l'instance boost::mpi::environment est de s'assurer que MPI a été initialisé et non laissé non finalisé et que tout cela est contrôlé par la durée de vie de l'objet. Par conséquent, vous devez en placer une instance où cette instance sera antérieure à toute activité MPI de votre programme et survécut à toute activité MPI de votre programme, et le meilleur endroit pour cela est la fonction main(). En outre, bien que les implémentations MPI-2 soient répandues de nos jours et que vous puissiez utiliser en toute sécurité la variante sans paramètre du constructeur de environment, il est préférable d'utiliser celle qui prend argc et argv de {{ X5}} pour des raisons de compatibilité. À moins que vous n'écriviez une bibliothèque, bien sûr.

Notez que les objets environment vérifient si MPI était déjà initialisé au moment de leur création et si c'est le cas, ils ne le finaliseront pas lorsqu'ils seront détruits, mais ils le finaliseront s'ils étaient à un pour l'initialiser. Ainsi, vous ne devriez pas avoir deux instances avec des durées de vie sans chevauchement. Par conséquent, vous devez vous assurer que l'ordre d'appel du constructeur et du destructeur est correct lorsque vous traitez des instances globales de la classe. Donc, restez simple et créez simplement une instance dans la fonction main().

Ce n'est pas le cas avec boost::mpi::communicator. Son constructeur par défaut encapsule simplement le descripteur du communicateur MPI_COMM_WORLD, qui est une constante d'exécution faisant référence au communicateur du monde MPI singleton. Puisque le mode d'encapsulation du constructeur par défaut est comm_attach, l'instance n'essaiera pas de libérer MPI_COMM_WORLD lors de la destruction, donc vous pouvez en avoir autant que vous le souhaitez et dans n'importe quelle partie de votre code. Gardez simplement à l'esprit que la plupart des méthodes d'instance ne fonctionnent qu'après l'initialisation et avant la finalisation de l'environnement MPI, qui définit la durée de vie de l'objet communicateur mondial réel (celui de l'implémentation MPI, pas l'instance de {{X4}) }).

Vous n'avez même pas besoin d'une instance statique de communicator car vous n'en avez besoin que pour accéder au communicateur mondial. Vous n'enregistrez qu'un seul appel à new et un autre à delete.

J'écrirais votre code simplement comme ceci:

#include <boost/mpi.hpp>
#include <iostream>

template<typename T>
class Example {
  boost::mpi::communicator world; 
public:
  Example() {
    std::cout << world.rank() << std::endl;
  }
};

int main(int argc, char **argv) {
  boost::mpi::environment(argc, argv);
  auto e = Example<double>();
}

Cela fonctionne comme prévu:

$ mpic++ -o works works.cc -lboost_mpi
$ mpiexec -n 4 ./works
2
0
3
1
1
Hristo Iliev 7 mai 2020 à 12:47

Eh bien, en suivant cette réponse, il est possible de créer des instances distinctes de world et env à l'intérieur de { {X2}}, mais si c'est souhaitable, je ne sais pas. Par exemple, cela semble fonctionner comme prévu:

#include <boost/mpi.hpp>
#include <iostream>

template<typename T>
class Example {
  static boost::mpi::environment env;
  static boost::mpi::communicator world; 
 public:
  Example() {
    std::cout << world.rank() << std::endl;
  }
};

template <typename T>
boost::mpi::environment Example<T>::env;

template <typename T>
boost::mpi::communicator Example<T>::world;

int main() {
  boost::mpi::environment env_in_main;
  boost::mpi::communicator world_in_main; 
  auto e = Example<double>();
}

En fait, cependant, la plupart des fonctionnalités peuvent être obtenues à l'aide du communicateur world. Il n'y a guère besoin de faire de env un membre de classe, donc peut-être que la meilleure solution est de garder world comme membre de classe, mais pas env.

0
innisfree 7 mai 2020 à 11:05

Les deux versions de votre programme provoquent un comportement indéfini comme indiqué dans boost::mpi documentation. Le fait que le premier fonctionne est apparemment un accident. Qu'est-ce qui ne va pas? Exactement ça:

Déclarer un environnement mpi :: à portée globale est un comportement indéfini.

Donc, en bref, mpi::environment devrait être créé au début de main.

1
bartop 7 mai 2020 à 08:40