一、说明
Boost.MPI 提供了 MPI 标准(消息传递接口)的接口。该标准简化了并发执行任务的程序的开发。您可以使用线程或通过共享内存或网络连接使多个进程相互通信来开发此类程序。 MPI 的优点是你不需要关心这些细节。您可以完全专注于并行化您的程序。
缺点是您需要 MPI 运行时环境。如果您控制运行时环境,MPI 只是一个选项。例如,如果你想分发一个可以通过双击启动的程序,你将无法使用 MPI。虽然操作系统开箱即用地支持线程、共享内存和网络,但它们通常不提供 MPI 运行时环境。用户需要执行额外的步骤来启动 MPI 程序。
- 开发和运行时环境
- 简单的数据交换
- 异步数据交换
- 集体数据交换
二、开发和运行时环境
MPI 定义了用于并行计算的函数。并行计算是指在支持任务并行执行的运行时环境中可以并发执行任务的程序。这样的运行时环境通常基于多个处理器。由于单个处理器只能顺序执行代码,因此链接多个处理器会创建一个可以并行执行任务的运行时环境。如果连接了数千个处理器,结果就是一台并行计算机——一种通常只在超级计算机中才能找到的架构。 MPI 来自于寻找更容易地为超级计算机编程的方法的搜索。
如果你想使用 MPI,你需要一个标准的实现。虽然 MPI 定义了许多功能,但它们通常不受开箱即用的操作系统支持。例如,Windows 的桌面版本不附带 MPI 支持。
最重要的 MPI 实现是 MPICH 和 Open MPI。 MPICH 是最早的 MPI 实现之一。它自 1990 年代中期就已存在。 MPICH 是一种成熟且可移植的实现,并得到积极维护和更新。 Open MPI 的第一个版本于 2005 年发布。由于 Open MPI 是一项协作成果,其中包括许多负责早期 MPI 实现的开发人员,因此 Open MPI 被视为未来的标准。然而,这并不意味着可以忽略 MPICH。有几种基于 MPICH 的 MPI 实现。例如,Microsoft 发布了一个名为 Microsoft HPC Pack 的 MPI 实现,它基于 MPICH。
MPICH 为各种操作系统(如 Windows、Linux 和 OS X)提供安装文件。如果您需要 MPI 实现并且不想从源代码构建它,MPICH 安装文件是开始使用 MPI 的最快途径。
MPICH 安装文件包含开发 MPI 程序所需的头文件和库。此外,它们还包含一个 MPI 运行时环境。因为 MPI 程序同时在多个处理器上执行任务,所以它们在多个进程中运行。一个 MPI 程序会启动多次,而不仅仅是一次。同一 MPI 程序的多个实例在多个处理器上运行,并通过 MPI 标准定义的函数进行通信。
您无法通过双击启动 MPI 程序。您使用一个帮助程序,通常称为 mpiexec。您将 MPI 程序传递给 mpiexec,它会在 MPI 运行时环境中启动您的程序。命令行选项确定启动了多少个进程以及它们如何通信——例如,通过套接字或共享内存。因为 MPI 运行时环境会处理这些细节,所以您可以专注于并行编程。
如果您决定使用 MPICH 的安装文件,请注意 MPICH 仅提供 64 位版本。您必须使用 64 位编译器通过 MPICH 开发 MPI 程序并构建 64 位版本的 Boost.MPI。
三、简单数据交换
Boost.MPI 是 MPI 标准的 C++ 接口。该库使用命名空间 boost::mpi。包含头文件 boost/mpi.hpp 就足以访问所有类和函数。
示例 47.1。 MPI 环境和通信器
#include#include int main(int argc, char *argv[]) { boost::mpi::environment env{argc, argv}; boost::mpi::communicator world; std::cout << world.rank() << ", " << world.size() << '\n'; }
Example47.1
示例 47.1 是一个简单的 MPI 程序。它使用两个类,您将在随后的所有示例中找到它们。 boost::mpi::environment 初始化 MPI。构造函数从 MPI 标准调用函数 MPI_Init()。析构函数调用 MPI_Finalize()。 boost::mpi::communicator 用于创建通信器。通信器是 MPI 的核心概念之一,支持进程之间的数据交换。
boost::mpi::environment 是一个非常简单的类,只提供了几个成员函数。您可以调用 initialized() 检查 MPI 是否已成功初始化。成员函数返回一个 bool 类型的值。 processor_name() 以 std::string 的形式返回当前进程的名称。 abort() 会停止 MPI 程序,而不仅仅是当前进程。您将一个 int 值传递给 abort()。该值将作为 MPI 程序的返回值传递到 MPI 运行时环境。对于大多数 MPI 程序,您不需要这些成员函数。您通常在程序开始时实例化 boost::mpi::environment,然后不使用该对象——如示例 47.1 和本章中的以下示例。
boost::mpi::communicator 更有趣。此类是一个通信器,它链接作为 MPI 程序一部分的进程。每个进程都有一个等级,它是一个整数——所有进程都会被枚举。进程可以通过在通信器上调用 rank() 来发现其等级。如果进程想知道有多少个进程,它会调用 size()。
要运行示例 47.1,您必须使用您正在使用的 MPI 实现提供的帮助程序。对于 MPICH,辅 助程序称为 mpiexec。您可以通过以下命令使用此帮助程序运行示例 47.1:
mpiexec-n4sample.exe
mpiexec 需要一个 MPI 程序的名称和一个告诉它要启动多少进程的选项。选项 -n 4 告诉 mpiexec 启动四个进程。因此 MPI 程序启动了四次。但是,这四个过程并不是独立的。它们通过 MPI 运行时环境链接,并且它们都属于同一个通信器,这给每个进程一个等级。如果您使用四个进程运行示例 47.1,rank() 返回一个从 0 到 3 的数字和 size() 4。
请注意,输出可能会混淆。毕竟,有四个进程同时写入标准输出流。例如,不知道排名为 0 或任何其他排名的进程是否是第一个写入标准输出流的进程。也有可能一个进程在写入标准输出流时会中断另一个进程。在另一个进程写入标准输出流之前,被中断的进程可能无法完成写入其等级和通信器的大小,从而破坏输出。
示例 47.2。发送和接收数据的阻塞函数
#include#include int main(int argc, char *argv[]) { boost::mpi::environment env{argc, argv}; boost::mpi::communicator world; if (world.rank() == 0) { int i; world.recv(1, 16, i); std::cout << i << '\n'; } else if (world.rank() == 1) { world.send(0, 16, 99); } }
boost::mpi::communicator 提供了两个简单的成员函数,send() 和 recv(),用于在两个进程之间交换数据。它们是阻塞函数,仅在发送或接收数据时才返回。这对于 recv() 尤其重要。如果在没有其他进程向其发送数据的情况下调用 recv(),调用将阻塞并且进程将在调用中停止。
在示例 47.2 中,等级为 0 的进程使用 recv() 接收数据。等级为 1 的进程使用 send() 发送数据。如果你用两个以上的进程启动程序,其他进程什么都不做就直接退出。
您将三个参数传递给 send():第一个参数是数据应发送到的进程的等级。第二个参数是用于识别数据的标签。第三个参数是数据。
标签始终是一个整数。在示例 47.2 中,标签是 16。标签可以识别对 send() 的调用。您会看到该标签与 recv() 一起使用。
传递给 send() 的第三个参数是 99。这个数字从等级 1 的进程发送到等级 0 的进程。Boost.MPI 支持所有原始类型。可以直接发送像 99 这样的 int 值。
recv() 需要类似的参数。第一个参数是应该从中接收数据的进程的等级。第二个参数是将对 recv() 的调用与对 send() 的调用链接起来的标签。第三个参数是存放接收到的数据的变量。
如果您使用至少两个进程运行示例 47.2,则会显示 99。
示例 47.3。从任何进程接收数据
#include#include int main(int argc, char *argv[]) { boost::mpi::environment env{argc, argv}; boost::mpi::communicator world; if (world.rank() == 0) { int i; world.recv(boost::mpi::any_source, 16, i); std::cout << i << '\n'; } else { world.send(0, 16, world.rank()); } }
Example47.3
示例 47.3 基于示例 47.2。它不发送数字 99,而是发送调用 send() 的进程的等级。这可以是等级大于 0 的任何进程。
对等级为 0 的进程的 recv() 调用也发生了变化。 boost::mpi::any_source 是第一个参数。这意味着对 recv() 的调用将接受来自任何发送带有标签 16 的数据的进程的数据。
如果您使用两个进程启动示例 47.3,将显示 1。毕竟,只有一个进程可以调用 send()——rank 为 1 的进程。如果你启动程序时有两个以上的进程,不知道会显示哪个数字。在这种情况下,多个进程将调用 send() 并尝试发送它们的排名。哪个进程最先,因此显示哪个排名是随机的。
示例 47.4。使用 boost::mpi::status 检测发件人
#include#include int main(int argc, char *argv[]) { boost::mpi::environment env{argc, argv}; boost::mpi::communicator world; if (world.rank() == 0) { int i; boost::mpi::status s = world.recv(boost::mpi::any_source, 16, i); std::cout << s.source() << ": " << i << '\n'; } else { world.send(0, 16, 99); } }
recv() 的返回值类型为 boost::mpi::status。此类提供了一个成员函数 source(),它返回从中接收数据的进程的等级。示例 47.4 告诉您数字 99 是从哪个进程收到的。
到目前为止,所有示例都使用 send() 和 recv() 来传输 int 值。在示例 47.5 中,传输了一个字符串。
示例 47.5。使用 send() 和 recv() 传输数组
#include#include int main(int argc, char *argv[]) { boost::mpi::environment env{argc, argv}; boost::mpi::communicator world; if (world.rank() == 0) { char buffer[14]; world.recv(boost::mpi::any_source, 16, buffer, 13); buffer[13] = '\0'; std::cout << buffer << '\n'; } else { const char *c = "Hello, world!"; world.send(0, 16, c, 13); } }
send() 和 recv() 可以传输数组和单个值。示例 47.5 传输字符数组中的字符串。因为 send() 和 recv() 支持像 char 这样的基本类型,所以可以毫无问题地传输 char 数组。
send() 将指向字符串的指针作为其第三个参数,并将字符串的大小作为其第四个参数。传递给 recv() 的第三个参数是指向存储接收数据的数组的指针。第四个参数告诉 recv() 应该接收多少个字符并将其存储在缓冲区中。示例 47.5 写出 Hello, world!到标准输出流。
示例 47.6。使用 send() 和 recv() 传输字符串
#include#include #include #include int main(int argc, char *argv[]) { boost::mpi::environment env{argc, argv}; boost::mpi::communicator world; if (world.rank() == 0) { std::string s; world.recv(boost::mpi::any_source, 16, s); std::cout << s << '\n'; } else { std::string s = "Hello, world!"; world.send(0, 16, s); } }
尽管 Boost.MPI 只支持原始类型,但这并不意味着它不能传输非原始类型的对象。 Boost.MPI 与 Boost.Serialization 一起工作。可以根据 Boost.Serialization 规则序列化的对象可以使用 Boost.MPI 进行传输。
示例 47.6 传输“Hello, world!”这次传输的值不是字符数组,而是 std::string。 Boost.Serialization 提供头文件 boost/serialization/string.hpp,只需要包含它以使 std::string 可序列化。
到此这篇关于C++ Boost MPI接口详细讲解的文章就介绍到这了,更多相关C++ Boost MPI内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!