boost::mpi 作为模板类的静态成员
Posted
技术标签:
【中文标题】boost::mpi 作为模板类的静态成员【英文标题】:boost::mpi as static member of templated class 【发布时间】:2020-08-22 11:33:28 【问题描述】:我想在我的班级中使用boost::mpi
通信器,因为我希望我的班级负责所有 MPI 调用。我使用这种样式使它们成为我班级的静态成员。
// 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。后来我想为我的课程模板。一开始我还担心如何初始化我的静态成员变量,但看了这个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.
...
这里出了点问题,但是什么?为什么?我想我在这里误解了一些事情。
【问题讨论】:
【参考方案1】:您的课程中需要boost::mpi::environment
实例有什么特别的原因吗?它只是围绕 MPI 当前单例性质的一个可怕的 OO 包装器 - 您可以通过调用 MPI_Init()
仅初始化 MPI 一次,然后通过调用 MPI_Finalize()
仅完成一次它(至少,直到 MPI 会话进入未来标准的版本)。如果您尝试在MPI_Init()
之前执行与 MPI 相关的任何操作,则会收到错误消息(少数有效的信息查询调用除外)。如果您在MPI_Finalize()
之后尝试执行与 MPI 相关的任何操作,则会收到错误消息。如果在调用 MPI_Init()
之后没有调用 MPI_Finalize()
就退出了单个 rank,则整个 MPI 作业通常会崩溃,因为启动器认为发生了错误并杀死了其余的 rank。
boost::mpi::environment
实例的唯一目的是确保 MPI 已初始化并且不会未完成,并且所有这些都由对象的生命周期控制。因此,您必须将它的一个实例放置在该实例将比您的程序中的任何 MPI 活动更早且寿命更长的位置,而最好的位置是 main()
函数。此外,虽然 MPI-2 实现现在很流行,并且您可以安全地使用 environment
构造函数的无参数变体,但为了兼容性起见,最好使用从 main()
获取 argc
和 argv
的那个。当然,除非您正在编写库。
请注意,environment
对象会检查 MPI 在创建时是否已经初始化,如果是,则在销毁时它们不会完成它,但如果它们要初始化它,它们将完成它。因此,您不应该有两个生命周期不重叠的实例。因此,在处理类的全局实例时,必须确保正确的构造函数和析构函数调用顺序。所以,保持简单,只需在 main()
函数中创建一个实例。
boost::mpi::communicator
不是这种情况。它的默认构造函数只是包装了MPI_COMM_WORLD
通信器句柄,这是一个运行时常量,指的是单例 MPI 世界通信器。由于默认构造函数的包装模式是comm_attach
,因此实例在销毁时不会尝试释放MPI_COMM_WORLD
,因此您可以在代码的任何部分拥有任意数量的这些。请记住,大多数实例方法仅在 MPI 环境初始化之后和最终确定之前起作用,这定义了实际世界通信器对象的生命周期(MPI 实现中的那个,而不是boost::mpi::communicator
的实例) .
您甚至不需要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
【讨论】:
我同意。我只是有问题的环境吗?在其他地方构建通信器可以吗? @innisfree 有一个单例世界通信器MPI_COMM_WORLD
,但它不是由communicator
类构造的——它只是在调用默认构造函数时被包装。这意味着您不能在 MPI 初始化之前构造此类对象,因为那是 MPI_COMM_WORLD
获取其实际值的时候(至少对于 Open MPI;它在基于 MPICH 的实现中具有特殊的常量值)。
@innisfree 我对 Open MPI 进行了更正 - MPI_COMM_WORLD
是静态结构的地址,因此它的值在初始化后不会改变。事实上,标准保证句柄是常量。【参考方案2】:
您的程序的两个版本都会导致未定义的行为,如 boost::mpi
documentation 中所述。第一个工作的事实显然是一个意外。怎么了?正是这样:
在全局范围内声明 mpi::environment 是未定义的行为。
所以简而言之,mpi::environment
应该在 main 的开头创建。
【讨论】:
啊,所以我应该将全局范围解释为包含我的私有静态成员变量? 它在技术上不是全局范围,但由于static
成员和全局变量之间的实际差异几乎没有,我会这样解释它。特别是如果被告知environment
使用argc
和argv
是的,有道理,谢谢。对我刚刚写给自己的(部分)答案的任何想法将不胜感激。例如,该代码的行为是否仍未定义?
@bartop,文档具有误导性。它不使用argc
和argv
,除非调用它们的重载构造函数。这与不允许将 NULL
传递给 MPI_Init()
的 MPI-2 之前的实现有关。【参考方案3】:
好吧,在这个answer 之后,可以通过在main
中创建world
和env
的单独实例,但是否可取,我不知道。例如,这似乎按预期工作:
#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。
【讨论】:
我会说它仍然是 UB 或至少使用未记录的功能,总而言之很容易导致 UB。我会坚持记录在案的内容以上是关于boost::mpi 作为模板类的静态成员的主要内容,如果未能解决你的问题,请参考以下文章