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
中创建 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
成为 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
我想在 我的 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
中创建 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
成为 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