C++ Boost MPI接口详细讲解

目录
  • 一、说明
  • 二、开发和运行时环境
  • 三、简单数据交换

一、说明

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 <boost/mpi.hpp>
#include <iostream>
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 <boost/mpi.hpp>
#include <iostream>
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 <boost/mpi.hpp>
#include <iostream>
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 <boost/mpi.hpp>
#include <iostream>
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 <boost/mpi.hpp>
#include <iostream>
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 <boost/mpi.hpp>
#include <boost/serialization/string.hpp>
#include <string>
#include <iostream>
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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-11-18

C++&nbsp;Boost&nbsp;PointerContainer智能指针详解

目录 一.提要 二.智能指针Boost.PointerContainer 三.练习 一.提要 在 C++11 中,Boost.PointerContainer是另一个智能指针,一般是用来生成集合数据的,本文阐述这种指针的特点和用法. 二.智能指针Boost.PointerContainer 库 Boost.PointerContainer 提供专门用于管理动态分配对象的容器.例如,在 C++11 中,您可以使用 std::vector<std::unique_ptr<int>> 创

C++&nbsp;Boost&nbsp;shared_ptr共享指针详细讲解

目录 一.提要 二.智能指针boost::shared_ptr与boost::scoped_ptr 三.智能指针 boost::shared_ptr用法 示例1 示例2 示例3 示例4 示例5 一.提要 boost::shared_ptr是另一个智能指针,与 boost::scoped_ptr有很大不同,本文阐述这种区别. 二.智能指针boost::shared_ptr与boost::scoped_ptr 主要区别在于: boost::shared_ptr 不一定是对象的独占所有者. 所有权可以

C++ Boost ScopeExit超详细讲解

目录 一.提要 二.退出作用域(Boost.ScopeExit) 2.1 范例1.UsingBOOST_SCOPE_EXIT 2.2 示例2.Boost.ScopeExit和C++11的lambda函数 2.3 示例3.特点BOOST_SCOPE_EXIT 三.练习 一.提要 资源有很多种,每种都封装一套,还是挺繁琐的!对于比较少使用或者一个程序很可能只会用一次的资源,我们不想封装,在这种情况下用Boost.ScopeExit. 二.退出作用域(Boost.ScopeExit) 库 Boost.

C++&nbsp;Boost&nbsp;Lambda表达式详解

目录 lambda表达式格式 说明Boost.Lambda lambda表达式格式 lambda表达式的格式 [捕捉列表](参数列表)mutable->返回值类型{ 语句部分 }; 其中参数列表.返回值类型是可选的,捕捉列表.函数体可以为空. 先来看一个较为简单的lamda表达式 int main(void) { auto add = [](int a, int b)->int {return a + b; }; cout << add(1, 2) << endl; r

C++&nbsp;boost&nbsp;scoped_ptr智能指针详解

目录 一.智能指针-唯一所有者 二.接口类分析 一.智能指针-唯一所有者 boost::scoped_ptr 是一个智能指针,它是动态分配对象的唯一所有者. boost::scoped_ptr 无法复制或移动.此智能指针在头文件 boost/scoped_ptr.hpp 中定义. 二.接口类分析 scoped_array 分析 scoped_array 的类部分原始代码如下: template<class T> class scoped_array // noncopyable { priva

C++ Boost weak_ptr智能指针超详细讲解

目录 一.提要 二.特别智能指针(Special Smart Pointers) 一.提要 在 C++11 中,boost::weak_ptr是另一类智能指针,一般是用COM组件生成.调用,本文阐述这种指针的特点和用法. 二.特别智能指针(Special Smart Pointers) 到目前为止介绍的每个智能指针都可以在不同的场景中单独使用.但是,boost::weak_ptr 仅在与 boost::shared_ptr 结合使用时才有意义. boost::weak_ptr 在 boost/w

C语言指针超详细讲解上篇

目录 前言 1.指针是什么 1.1 指针变量 1.2 指针是内存中一个最小单元的编号 2.指针和指针类型 2.1 指针±类型 2.2 指针的解引用 2.2.1 int* 类型的解引用 2.2.2 char* 类型的解引用 3.野指针 3.1 野指针成因 3.1.1 指针未初始化 3.1.2 指针越界访问 3.1.3 指针指向的空间释放 3.2 如何规避野指针 总结 前言 本文开始指针相关内容的学习,主要内容包括: 指针是什么 指针和指针类型 野指针 指针运算 指针和数组 二级指针 指针数组 1.

C语言指针超详细讲解下篇

目录 前言 指针运算 指针±整数 4.1 指针±整数 4.2 指针-指针 4.3 指针的关系运算 5.指针和数组 6.二级指针 7.指针数组 7.1 举例 1 7.2 举例 2 总结 前言 本文接着上一篇内容,继续学习指针相关知识点. 指针运算 指针±整数 指针-指针 指针的关系运算 4.1 指针±整数 #define VALUE 5 int main() { float values[VALUE]; float *vp; //指针+-指针,关系运算 for (vp = &values[0];

C++超详细讲解智能指针

目录 一.内存泄漏-永恒的话题 二.深度思考 三.智能指针分析 四.小结 一.内存泄漏-永恒的话题 动态申请堆空间,用完后不归还 C++ 语言中没有垃圾回收的机制 指针无法控制所指堆空间的生命周期 下面看一段内存泄漏的代码: #include <iostream> #include <string> using namespace std; class Test { int i; public: Test(int i) { this->i = i; } int value()

C++&nbsp;Boost&nbsp;Optional示例超详细讲解

目录 一.概述 二.Boost.Optional 一.概述 数据结构类似于容器,因为它们可以存储一个或多个元素.但是,它们与容器不同,因为它们不支持容器通常支持的操作.例如,使用本部分介绍的数据结构,不可能在一次迭代中访问所有元素. Boost.Optional 可以很容易地标记可选的返回值.使用 Boost.Optional 创建的对象要么是空的,要么包含单个元素.使用 Boost.Optional,您无需使用空指针或 -1 等特殊值来指示函数可能没有返回值. Boost.Tuple 提供了

C语言超详细讲解指针的概念与使用

目录 一.指针与一维数组 1. 指针与数组基础 2. 指针与数组 3. 一个思考 二.指针与字符串 三.指针和二维数组 1. 指针数组与数组指针 2. 指针数组 3. 数组指针 一.指针与一维数组 1. 指针与数组基础 先说明几点干货: 1. 数组是变量的集合,并且数组中的多个变量在内存空间上是连续存储的. 2. 数组名是数组的入口地址,同时也是首元素的地址,数组名是一个地址常量,不能更改. 3. 数组的指针是指数组在内存中的起始地址,数组元素的地址是指数组元素在内存中的其实地址. 对于第一点数

C语言超详细讲解指针的使用

目录 指针概述 自身类型 指向类型 代码例子 数值型指针 字符型指针 单字符 字符数组 字符串型指针 字符数组总结 指针概述 C语言中指针也可以认为是一种类型,不同于数值型和字符型的类型.推演过去指针变量也就是相当于不同的变量类型,不同于数值变量.字符型变量.字符串变量. 指针变量两种类型:自身类型和指向的类型 自身类型:将变量名去掉,剩下的就是指针变量类型. 指向类型:将变量名和离它最近的一个*去掉,剩下的类型就是指针指向的类型 int num = 10; int* p = NULL; p =

C++超详细讲解内存空间分配与this指针

目录 成员属性和函数的存储 空对象 成员属性的存储 成员函数的存储 this指针的概念 解决名称冲突 返回对象指针*this 总结 成员属性和函数的存储 在C++中成员变量和成员函数是分开存储的: 空对象 class Person {}; 这里我直接创建一个空的类,并创建一个空的类对象(Person p),利用sizeof关键字输出p所占内存空间,sizeof(p);结果是p=1: 注意:空对象占用内存空间为: 1.C++编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置 2.每

C++超详细讲解引用和指针

目录 引用概念 定义步骤 引用必须初始化 引用初始化后不能更改 引用作为函数的参数可以替代指针变量 常引用 引用作为函数的返回值类型 引用的本质 指针的引用(了解) 指针和引用的区别 引用概念 引用的本质:给已有的变量名 取个别名 //给num取个别名为b int num =100; //&不是取b的地址 只是描述b是num的别名 编译器不会为b开辟新的空间 int &b = num;//num的别名 是b //操作b等价操作num 定义步骤 1.&修饰别名 2.给哪个变量取别名

C语言超详细讲解宏与指针的使用

目录 1.关于define 2.初识指针 (1)内存 (2)示例 (3)指针的使用示例 (4)指针变量的大小 1.关于define define是一个预处理指令,有两种用法,一种是用define定义常量:另外一种是define定义宏. 下面的例子为利用define定义常量 #define _CRT_SECURE_NO_WARNINGS #define MAX 1000 #include <stdio.h> int main() { printf("%d\n",MAX); r

C语言超详细讲解函数指针的运用

目录 前言 计算器的例子 回调函数 转移表 前言 前面我们学习了各种各样的指针类型,有些指针可以说是稀奇百怪,特别是函数指针,有些朋友可能觉得,函数指针有些多余,调用函数为什么要用指针调用,直接调用不好吗? 接下来我们从具体的实例来回答同学们的问题,加深对函数指针的理解. 计算器的例子 接下来我们写一个简单的计算器程序,完成不同的计算功能比如加减乘除: #include <stdio.h> //相加函数 int add(int a, int b) { return a + b; } //相减函