boost :: mpi as stati c член шаблонного класса - PullRequest
2 голосов
/ 07 мая 2020

Я хотел использовать boost::mpi коммуникаторы в моем классе, потому что я хочу, чтобы мой класс заботился обо всех вызовах MPI. Я использовал этот стиль, чтобы сделать их c членами моего класса.

// 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();
}

Это прекрасно работает, например, mpirun -n 4 ./works печатает числа от 0 до 3, как и ожидалось. Позже я хотел создать шаблон своего класса. Сначала я беспокоился о том, как инициализировать свои переменные-члены stati c, но прочел этот ответ , в котором говорилось, что все в порядке

// 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>();
}

Однако это фактически дает мне

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

Здесь что-то не так, но что? и почему? Думаю, я здесь кое-что неправильно понимаю.

Ответы [ 3 ]

2 голосов
/ 07 мая 2020

По какой конкретной причине вам нужен экземпляр boost::mpi::environment в ваших классах? Это просто ужасная объектно-ориентированная оболочка вокруг текущей одноэлементной природы MPI - вы можете инициализировать MPI только один раз, вызвав MPI_Init(), и завершить его только один раз впоследствии, вызвав MPI_Finalize() (по крайней мере, до тех пор, пока сеансы MPI не перейдут в будущее. версии стандарта). Если вы попытаетесь сделать что-нибудь, связанное с MPI до MPI_Init(), вы получите сообщение об ошибке (за исключением нескольких работающих вызовов информационных запросов). Если вы попытаетесь сделать что-либо, связанное с MPI, после MPI_Finalize(), вы получите сообщение об ошибке. Если один ранг выходит без вызова MPI_Finalize() после вызова MPI_Init(), вся работа MPI обычно завершается сбоем, поскольку программа запуска предполагает, что что-то случилось, и убивает остальные ранги.

Единственная цель boost::mpi::environment должен гарантировать, что MPI был инициализирован, а не оставлен незавершенным, и все это контролируется временем жизни объекта. Следовательно, вы должны разместить его экземпляр там, где этот экземпляр будет предшествовать и переживет любую активность MPI в вашей программе, и лучшим местом для этого является функция main(). Кроме того, хотя реализации MPI-2 широко распространены в настоящее время, и вы можете безопасно использовать вариант конструктора environment без параметров, для совместимости лучше использовать тот, который принимает argc и argv из main(). Если, конечно, вы не пишете библиотеку.

Обратите внимание, что environment объекты проверяют, был ли MPI уже инициализирован к моменту их создания, и если да, они не завершат его при уничтожении, но они будут завершить его, если они должны были его инициализировать. Таким образом, у вас не должно быть двух экземпляров с неперекрывающимися сроками жизни. Следовательно, вы должны обеспечить правильный порядок вызова конструктора и деструктора при работе с глобальными экземплярами класса. Так что оставайтесь простыми и просто создайте один экземпляр в функции main().

Это не относится к boost::mpi::communicator. Его конструктор по умолчанию просто обертывает дескриптор коммуникатора MPI_COMM_WORLD, который является постоянной времени выполнения, относящейся к одноэлементному мировому коммуникатору MPI. Поскольку режим упаковки конструктора по умолчанию - comm_attach, экземпляр не будет пытаться освободить MPI_COMM_WORLD при уничтожении, поэтому у вас может быть столько из них, сколько вам нужно, и в любой части вашего кода. Просто имейте в виду, что большинство методов экземпляра работают только после инициализации и до завершения среды MPI, что и определяет время жизни фактического объекта мирового коммуникатора (тот, что в реализации MPI, а не экземпляр boost::mpi::communicator). .

Вам даже не нужен статический c экземпляр communicator, он нужен только для доступа к мировому коммуникатору. Вы сохраняете только один вызов new и один delete.

Я бы написал ваш код просто так:

#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>();
}

Он работает, как ожидалось:

$ mpic++ -o works works.cc -lboost_mpi
$ mpiexec -n 4 ./works
2
0
3
1
1 голос
/ 07 мая 2020

Обе версии вашей программы вызывают неопределенное поведение, как указано в boost::mpi документации . То, что первая работает, видимо случайно. Что случилось? Именно так:

Объявление mpi :: environment в глобальной области действия является неопределенным поведением.

Короче говоря, mpi::environment должен быть создан в начале main.

0 голосов
/ 07 мая 2020

Что ж, после этого ответа можно создать отдельные экземпляры world и env внутри main, но целесообразно ли это, я не знаю. Например, похоже, что это работает как ожидалось:

#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>();
}

На самом деле, однако, большая часть функциональности может быть достигнута с помощью коммуникатора world. Нет необходимости делать env членом класса, поэтому, возможно, лучшим решением будет сохранить world как член класса, но не env.

...