boost::mpi 作为模板化的静态成员 class

boost::mpi as static member of templated class

我想在 我的 class 中使用 boost::mpi 通信器 ,因为我希望我的 class 处理所有 MPI 调用。我使用这种样式使它们成为我的 class.

的静态成员
// 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。后来我想模板化我的 class。起初我担心如何初始化我的静态成员变量,但是阅读这个answer,它表明一切都很好

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

这里出了点问题,但是什么?为什么?我想我在这里误解了一些事情。

boost::mpi documentation 中所述,您的程序的两个版本都会导致未定义的行为。首先起作用的事实显然是个意外。怎么了?正是这样:

Declaring an mpi::environment at global scope is undefined behavior.

所以简而言之,mpi::environment 应该在 main 的开头创建。

好吧,按照这个 answer,可以通过在 main 中创建 worldenv 的单独实例,但是我不知道这样做是否可取.例如,这似乎按预期工作:

#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 成为 class 成员,所以也许最好的解决方案是让 world 成为 class 成员,而不是 env.

您在 classes 中需要一个 boost::mpi::environment 实例有什么特别的原因吗?它只是围绕 MPI 当前单例性质的一个可怕的 OO 包装器——您可以通过调用 MPI_Init() 仅初始化 MPI 一次,然后通过调用 MPI_Finalize() 仅完成一次(至少,直到 MPI 会话进行它进入标准的未来版本)。如果您在 MPI_Init() 之前尝试执行任何与 MPI 相关的操作,您会收到错误消息(除了少数有效的信息查询调用之外)。如果您在 MPI_Finalize() 之后尝试执行任何与 MPI 相关的操作,您会收到错误消息。如果一个 rank 在调用 MPI_Init() 后没有调用 MPI_Finalize() 就退出了,整个 MPI 作业通常会崩溃,因为启动器假设发生了错误并杀死了其余的 ranks。

boost::mpi::environment 实例的唯一目的是确保 MPI 已初始化而不是未完成,所有这些都由对象的生命周期控制。因此,您必须放置它的一个实例,该实例将早于程序中的任何 MPI activity 并且比任何 MPI activity 都存在,而最好的地方是 main() 函数。此外,尽管 MPI-2 实现现在很流行并且您可以安全地使用 environment 构造函数的无参数变体,但最好使用从 main() 为了兼容性。当然,除非你正在编写一个库。

请注意,environment 对象会检查 MPI 在创建时是否已经初始化,如果是,则它们不会在销毁时完成它,但如果要进行初始化,它们将完成它它。因此,您不应该有两个生命周期不重叠的实例。因此,在处理 class 的全局实例时,必须确保正确的构造函数和析构函数调用顺序。所以,保持简单,只在 main() 函数中创建一个实例。

boost::mpi::communicator 并非如此。它的默认构造函数仅包装 MPI_COMM_WORLD 通信器句柄,这是一个 运行 时间常数,指的是单例 MPI 世界通信器。由于默认构造函数的包装模式是 comm_attach,实例不会在销毁时尝试释放 MPI_COMM_WORLD,因此您可以在代码的任何部分中拥有任意数量的包装模式。请记住,大多数实例方法仅在初始化之后和 MPI 环境完成之前起作用,这定义了实际世界通信器对象的生命周期(MPI 实现中的那个,而不是 [=27= 的实例) ]).

您甚至不需要 communicator 的静态实例,因为您只需要它来访问 world 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