MPI基础知识

Basic Knowledge for MPI

Reference: Message Passing Interface, Blaise Barney, Lawrence Livermore National Laboratory
MPI:Message Passing Interfaces Standard, 消息传递接口标准,不是一门语言。

MPI的目标是建立一个可移植、高效和灵活的信息传递标准,用于编写信息传递程序

MPI不是IEEE和ISO标准,但事实上已成为HPC平台上编写消息传递程序的“行业标准”

MPI 是针对消息传递库的开发人员和用户的规范。就其本身而言,它不是一个库——而是这样一个库应该是什么的规范。即为编写消息传递程序而制定的标准。

MPI 主要解决消息传递并行编程模型:通过对每个进程的协作操作,数据从一个进程的地址空间移动到另一个进程的地址空间。

MPI有很多版本,目前最新版本是MPI-3.x,需要注意不同MPI库所支持的MPI标准的版本和功能有所不同。

1 Introduction to MPI

1.1 计算模型

  • 从上世纪80-90年代初的分布式内存架构,到后来的共享内存架构(shared memory SMPS)经网络组合形成的混合分布式内存/共享内存系统,MPI开发者调整了库以无缝适应两种类型的底层架构。
  • 目前,MPI几乎可在所有硬件平台运行,例如分布式内存,共享内存和杂交式内存。但无论机器底层架构如何,编程模型仍然是分布式的。
  • 所有并行(的实现)都是明确的:程序员负责正确识别并行并使用 MPI 构造实现并行算法。

1.2 使用MPI的意义

  • 标准化,几乎在所有HPC平台支持,并代替之前的所有信息传递标准。
  • 可移植性,应用程序移植到不同MPI标准的平台,几乎不需要修改源代码。
  • 可升级性,供应商易于基于该标准对硬件进行优化,任何实现方式窦娥能获得优化算法。
  • 功能性,MPI-3提供超过430个例程(routines),一般大多数MPI程序可用十几个或更少例程编写。
  • 可用性,供应商和公共领域都有各种实现可用。

1.3 历史与渊源

  • 1980s to early 1990s, 分布式内存、并行计算以及许多用于编写此类程序的不兼容软件工具都在发展 - 通常在可移植性、性能、功能和价格之间进行权衡。出现了对标准必要性的认识。
  • 1992年4月,分布式内存环境中消息传递标准研讨会,由弗吉尼亚州威廉斯堡并行计算研究中心赞助。讨论了标准消息传递接口必不可少的基本特性,并成立了一个工作组来继续标准化过程。随后制定了初步提案草案。
  • 1992年11月,工作组在明尼阿波利斯开会。提交了 ORNL 的 MPI 提案草案 (MPI1)。集团采用程序和组织方式形成MPI论坛。它最终由来自 40 个组织的约 175 人组成,其中包括并行计算机供应商、软件编写者、学术界和应用科学家。
  • 1993年11月,超级计算 93 会议 - 提出 MPI 标准草案。
  • 1994年5月,Final version of MPI-1.0 released
    • MPI-1.1 (Jun 1995)
    • MPI-1.2 (Jul 1997)
    • MPI-1.3 (May 2008).
  • 1998年,MPI-2 从第一个 MPI 规范停止的地方开始,并解决了远远超出 MPI-1 规范的主题。
    • MPI-2.1(2008 年 9 月)
    • MPI-2.2(2009 年 9 月)
  • 2012 年 9 月:MPI-3.0 标准获得批准。
    • MPI-3.1(2015 年 6 月)
  • 目前,MPI-4.0正在研究。

1.4 文档

有关MPI标准所有版本的文档可下载于:http://www.mpi-forum.org/docs/

2 MPI实现与编译器

具体MPI库实现通常要考虑很多问题,包括又不仅限于:

  • 支持哪个版本的 MPI 标准?
  • 是否支持特定 MPI 版本中的所有功能?
  • 是否添加了任何新功能?
  • 支持哪些网络接口?
  • MPI 应用程序是如何编译的?
  • MPI 作业是如何启动的?
  • 运行时环境变量控制?

LC系统上的MPI库实现方式各不相同,它们所使用的编译器也不尽相同。下表对这些情况进行了总结。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sMEfW1En-1645012844058)(./image/MPI-libraryImplementations.png)]
下面对这些库进行简要介绍。

2.1 选择MPI库和编译器

  • LC 为每个集群提供一个默认的 MPI 库;LC 还为每个集群提供默认编译器;通常,每个集群上都有多个版本的 MPI 库和编译器。
  • 可使用不同功能模块选择特定MPI库或编译器,例如:列出当前加载的模块、显示所有可用模块、加载不同的 MPI 模块、加载不同的编译器模块、确认新加载的模块。
2.1.1 MVAPICH

MPI标准的一种表现

  • 默认 MPI 实现
  • 多个版本可用
  • MPI-2 和 MPI-3 实现基于来自阿贡国家实验室的 MPICH MPI 库。根据开发人员的文档,1.9 及更高版本实现了 MPI-3。
  • 线程安全
    要查询可用版本或选择可选版本,使用模块命令,例如:
module avail mvapich			(list available modules)
module load mvapich2/2.3		(use the module of interest)
编译

在MPI build scripts表中查询

执行

使用具有相应选项的Slurm srun命令启动MPI可执行文件。例如,要在pdebug 池中的两个不同节点上启动一个 8 进程 MPI 作业:

srun -N2 -n8 -ppdebug a.out

Linux 集群概述教程的运行作业部分详细讨论了 srun 命令。

文档
2.1.2 Open MPI

模块命令:

module avail                 (list available modules)
module load openmpi/3.0.1    (use the module of interest)

这可确保 LC 的 MPI 包装器脚本指向所需版本的 Open MPI。

编译

在MPI build scripts表中查询

执行

确保在链接(build)可执行程序时加载了相同的Open MPI模块。如果你运行了批处理程序,你需要在批处理脚本中加载模块。
可以使用以下命令启动 Open MPI 作业。例如,要运行 48 个进程的 MPI 作业:

mpirun -np 48 a.out
mpiexec -np 48 a.out
srun -n 48 a.out
文档

OpenMPI主页:http://www.open-mpi.org/

2.1.3 Intel MPI
2.1.4 CORAL 抢先版和 Sierra 集群:
2.1.5 MPI链接(build)脚本
  • LC 开发的 MPI 编译器包装脚本用于在所有 LC 系统上编译 MPI 程序。
  • 自动执行一些错误检查,包括适当的 MPI #include 文件,链接到必要的 MPI 库,并将选项传递给底层编译器。
    • 注意:可能需要为所需的 MPI 实现加载一个模块,如前所述。不这样做将导致获得默认实现。
  • 下表列出了 LC 的 Linux 集群的主要 MPI 编译器包装器脚本。对于 CORAL EA / Sierra 系统,请参阅上面提供的链接。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3y7foKt-1645012844059)(./image/MPI-BuildScripts.png)]
2.1.6 线程支持级别
  • MPI 库的线程支持级别各不相同:
    • MPI_THREAD_SINGLE - 级别 0:只有一个线程将执行。
    • MPI_THREAD_FUNNELED - 级别 1:进程可能是多线程的,但只有主线程会进行 MPI 调用 - 所有 MPI 调用都汇集到主线程。
    • MPI_THREAD_SERIALIZED - Level 2:进程可能是多线程的,多个线程可能会进行 MPI 调用,但一次只能调用一个。也就是说,由于所有 MPI 调用都是序列化的,因此不会从两个不同的线程同时进行调用。
    • MPI_THREAD_MULTIPLE - 级别 3:多个线程可以不受限制地调用 MPI。

3 Getting Started

  • 一般的MPI程序结构包括:
    MPI 包含文件(include file) -> 初始化 MPI环境(并行代码开始) -> 执行和信息传递调用 -> 中止MPI环境(并行代码结束)

  • 头文件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nrV2V5vM-1645012844059)(./image/MPI-HeaderFile.png)]

  • MPI调用格式
    C语言区分大小写,Fortran不区分大小写。禁止使用前缀MPI_PMPI_声明变量或函数。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x1rrIoZB-1645012844059)(./image/MPI-FormatofCall.png)]

  • 通讯器(commuincators)和组(groups)
    MPI 使用称为通信器和组的对象来定义哪些进程集合可以相互通信。
    大多数 MPI 例程要求您指定一个通信器作为参数。
    稍后将更详细地介绍通信器和组。现在,只要需要通信器,只需使用 MPI_COMM_WORLD - 它是包含所有 MPI 进程的预定义通信器。

  • 等级(rank)
    在通信器中,每个进程都有自己唯一的整数标识符,由系统在进程初始化时分配。等级有时也称为“任务 ID”。等级是连续的并且从零开始。
    程序员使用它来指定消息的来源和目的地。通常由应用程序有条件地用于控制程序执行(如果 rank=0 执行此操作/如果 rank=1 执行此操作)。

  • 错误处理
    大多数 MPI 例程都包含返回/错误代码参数,如上面“MPI 调用的格式”部分所述。
    但是,根据 MPI 标准,如果出现错误,MPI 调用的默认行为是中止。这意味着您可能无法捕获 MPI_SUCCESS(零)以外的返回/错误代码。
    该标准确实提供了一种覆盖此默认错误处理程序的方法。有关如何执行此操作的讨论可在此处获得。您还可以查阅位于 http://www.mpi-forum.org/docs/ 的相关 MPI 标准文档的错误处理部分。
    向用户显示的错误类型取决于实现方法。

4 环境管理例程(Environment Management Routines)

这组例程用于查询和设置 MPI 执行环境,涵盖了多种用途,例如初始化和终止 MPI 环境、查询 rank 的身份、查询 MPI 库的版本等。 大多数常用的描述如下。

  • MPI_Init
    初始化 MPI 执行环境。该函数必须在每个 MPI 程序中调用,必须在任何其他 MPI 函数之前调用,并且只能在 MPI 程序中调用一次。对于 C 程序, MPI_Init 可用于将命令行参数传递给所有进程,尽管这不是标准要求的并且取决于实现。
MPI_Init (&argc,&argv)
MPI_INIT (ierr)
  • MPI_Comm_size
    返回指定通信器中的 MPI 进程总数,例如 MPI_COMM_WORLD。如果通信器是 MPI_COMM_WORLD,那么它代表您的应用程序可用的 MPI 任务数。
MPI_Comm_size (comm,&size)
MPI_COMM_SIZE (comm,size,ierr)
  • MPI_Comm_rank
    返回指定通信器中调用 MPI 进程的等级。最初,在通信器 MPI_COMM_WORLD 中,每个进程将被分配一个唯一的整数等级,介于 0 和任务数 - 1 之间。此排名通常称为任务 ID。如果一个进程与其他通信器相关联,它也将在每个通信器中具有唯一的等级。
MPI_Comm_rank (comm,&rank)
MPI_COMM_RANK (comm,rank,ierr)
  • MPI_Abort
    Terminates all MPI processes associated with the communicator. In most MPI implementations it terminates ALL processes regardless of the communicator specified.
MPI_Abort (comm,errorcode)
MPI_ABORT (comm,errorcode,ierr)
  • MPI_Get_processor_name
    返回处理器名称。还返回名称的长度。 “name”的缓冲区大小必须至少为 MPI_MAX_PROCESSOR_NAME 个字符。返回到“name”的内容取决于实现——可能与“hostname”或“host”shell命令的输出不同。
MPI_Get_processor_name (&name,&resultlength)
MPI_GET_PROCESSOR_NAME (name,resultlength,ierr)
  • MPI_Get_version
    返回库使用的MPI版本和子版本。
MPI_Get_version (&version,&subversion)
MPI_GET_VERSION (version,subversion,ierr)
  • MPI_Initialized
    指示是否已调用 MPI_Init - 将标志返回为逻辑真 (1) 或假 (0)。 MPI 要求 MPI_Init 被每个进程调用一次并且只能调用一次。这可能会给想要使用 MPI 并准备在必要时调用 MPI_Init 的模块带来问题。 MPI_Initialized 解决了这个问题。
MPI_Initialized (&flag)
MPI_INITIALIZED (flag,ierr)
  • MPI_Wtime
    返回调用处理器上经过的时钟时间(以秒为单位)(双精度)。
MPI_Wtime ()
MPI_WTIME ()
  • MPI_Wtick
    返回 MPI_Wtime 的秒分辨率(双精度)。
MPI_Wtick ()
MPI_WTICK ()
  • MPI_Finalize
    终止 MPI 执行环境。这个函数应该是每个 MPI 程序中调用的最后一个 MPI 例程——在它之后不能调用其他 MPI 例程。
MPI_Finalize ()
MPI_FINALIZE (ierr)
  • Fortran语言下的环境管理例程示例
program simple

! required MPI include file
include 'mpif.h'

integer numtasks, rank, len, ierr
character(MPI_MAX_PROCESSOR_NAME) hostname

! initialize MPI
call MPI_INIT(ierr)

! get number of tasks
call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr)

! get my rank
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)

! this one is obvious
call MPI_GET_PROCESSOR_NAME(hostname, len, ierr)
print *, 'Number of tasks=',numtasks,' My rank=',rank,' Running on=',hostname


    ! do some work with message passing


! done with MPI
call MPI_FINALIZE(ierr)

end

5 练习1

  • 步骤
  1. Login to an LC cluster using your workshop username and OTP token
  2. Copy the exercise files to your home directory
  3. Familiarize yourself with LC’s MPI compilers
  4. Write a simple “Hello World” MPI program using several MPI Environment Management routines
  5. Successfully compile your program
  6. Successfully run your program - several different ways
    详情点击https://hpc-tutorials.llnl.gov/mpi/exercise_1/

6 点对点通信例程(point to point commuication)

6.1 一般概念

对于一个计算Pi的简单例子,将问题分成若干个互相需要信息传递的问题,是保证并行的关键,否则仅仅是串行代码,代码之间没有信息的传递。

  • 点对点操作的类型:
    MPI 点对点操作通常涉及两个且仅两个不同的 MPI 任务之间的消息传递。一个任务是执行发送操作,另一个任务是执行匹配的接收操作。下面是用于不同目的的发送和接收例程:

    • sunchronous send(同步发送)
    • blocking send / blocking receive (阻塞发送,阻塞接收)
    • non-blocking send / non-blocking receive (非阻塞发送,非阻塞接收)
    • buffered send (缓冲发送)
    • combined send /receive (组合发送/接收)
    • “ready” send (ready发送)
      任意类型的发送类型都可与任意类型的接收例程相匹配。MPI 还提供了几个与发送 - 接收操作相关的例程,例如用于等待消息到达或探查消息是否已到达的例程。
  • 缓存(buffering)
    最理想的情况是,每个发送操作都与其相匹配的接收操作完全同步,但这种情况很少见。因此,当两个任务不同步时,MPI的实现必须能够存储数据。这里对两种情况进行讨论:

    • 发送操作在接收准备就绪前5秒发生。接收未决时的信息在哪里?
    • 多个发送同时到达同一接收任务,一次只能接受一个发送。“备份”的消息会发生什么?
      MPI 实现(不是 MPI 标准)决定了在这些类型的情况下数据会发生什么。通常,系统缓冲区被保留用于保存传输中的数据。

    系统缓存空间:

    • 对程序员不透明,完全由 MPI 库管理
    • 是一种很容易耗尽的有限资源
    • 通常很神秘且没有很好的记录
    • 能够存在于发送方、接收方或两者
    • 可以提高程序性能的东西,因为它允许发送 - 接收操作是异步的。
      用户管理的地址空间(即您的程序变量)称为应用程序缓冲区。 MPI 还提供用户管理的发送缓冲区。
  • 阻塞与非阻塞
    大多数MPI点对点例程可用阻塞或非阻塞模式。

    • 阻塞(blocking)
      • 阻塞发送例程只会在修改应用程序缓冲区(您的发送数据)以供重用是安全的后才会“返回”。安全意味着修改不会影响用于接收任务的数据。安全并不意味着数据实际上已收到——它很可能位于系统缓冲区中。
      • 阻塞发送可以是同步的,这意味着接收任务会发生握手以确认安全发送。
      • 如果系统缓冲区用于保存数据以最终交付给接收方,则阻塞发送可以是异步的。
      • 阻塞接收仅在数据到达并准备好供程序使用后“返回”。
    • 非阻塞(non-blocking)
      • 非阻塞发送和接收例程的行为类似——它们几乎会立即返回。它们不等待任何通信事件完成,例如消息从用户内存复制到系统缓冲区空间或消息实际到达。
      • 非阻塞操作只是“请求”MPI 库在可能的情况下执行操作。用户无法预测何时会发生。
      • 修改应用程序缓冲区(您的变量空间)是不安全的,除非您知道所请求的非阻塞操作实际上是由库执行的。有用于执行此操作的“等待”例程。
      • 非阻塞通信主要用于将计算与通信重叠并利用可能的性能提升。
  • 顺序和平等性(order and fairness)

    • 顺序(order)
      • MPI 保证消息不会相互超越。
      • 如果发送方连续向同一目的地发送两条消息(消息 1 和消息 2),并且都匹配相同的接收,则接收操作将在消息 2 之前接收消息 1。
      • 如果接收者连续发布两个接收(接收 1 和接收 2),并且两者都在寻找相同的消息,则接收 1 将在接收 2 之前接收该消息。
      • 如果有多个线程参与通信操作,则顺序规则不适用。
    • 平等性(fairness)
      • MPI不保证平等性。防止“operation starvation”取决于程序员。
      • 例如:任务 0 向任务 2 发送消息。 然而,任务 1 发送与任务 2 的接收相匹配的竞争消息。只有一个发送将完成。

6.2 MPI信息传递例程参数

MPI 点对点通信例程通常具有采用以下格式之一的参数列表:

  1. 阻塞发送:MPI_Send(buffer,count,type,dest,tag,comm)
  2. 非阻塞发送:MPI_Isend(buffer,count,type,dest,tag,comm,request)
  3. 阻塞接收:MPI_Recv(buffer,count,type,source,tag,comm,status)
  4. 非阻塞接收:MPI_Irecv(buffer,count,type,source,tag,comm,request)
  • 缓存
    引用要发送或接收的数据的程序(应用程序)地址空间。在大多数情况下,这只是发送/接收的变量名称。对于 C 程序,这个参数是通过引用传递的,并且通常必须在前面加上一个 & 符号:&var1
  • 数据计数
    指示要发送的特定类型的数据元素的数量。
  • 数据类型
    出于可移植性的原因,MPI 预定义了其基本数据类型。下表列出了标准要求的内容。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9KbFXJaf-1645012844060)(./image/MPIArguments.png)]
  • 目的地(destination)
    发送例程的参数,指示应将消息传递到的进程。指定为接收进程的等级。
  • 源(source)
    接收例程的参数,指示消息的发起过程。指定为发送进程的等级。这可以设置为通配符 MPI_ANY_SOURCE 以接收来自任何任务的消息。
  • 标签(tag)
    程序员分配的任意非负整数,用于唯一标识消息。发送和接收操作应该匹配消息标签。对于接收操作,通配符 MPI_ANY_TAG 可用于接收任何消息,而不管其标签如何。 MPI 标准保证整数 0-32767 可以用作标记,但大多数实现允许比这更大的范围。
  • 通信器(communicator)
    指示通信上下文或源或目标字段对其有效的一组进程。除非程序员明确创建新的通信器,否则通常使用预定义的通信器 MPI_COMM_WORLD。
  • 状态(status)
    对于接收操作,指示消息的来源和消息的标签。在 C 中,这个参数是一个指向预定义结构 MPI_Status(例如 stat.MPI_SOURCE stat.MPI_TAG)的指针。在 Fortran 中,它是一个大小为 MPI_STATUS_SIZE 的整数数组(例如 stat(MPI_SOURCE) stat(MPI_TAG))。此外,接收到的实际字节数可通过 MPI_Get_count 例程从 Status 中获得。如果稍后将查询消息的来源、标签或大小,则可以替换常量 MPI_STATUS_IGNORE 和 MPI_STATUSES_IGNORE。
  • 请求(request)
    由非阻塞发送和接收操作使用。由于非阻塞操作可能会在获得请求的系统缓冲区空间之前返回,因此系统会发出唯一的“请求号”。程序员稍后(在 WAIT 类型例程中)使用此系统分配的“句柄”来确定非阻塞操作的完成。在 C 中,这个参数是一个指向预定义结构 MPI_Request 的指针。在 Fortran 中,它是一个整数。

6.3 阻塞信息传递

下面描述了更常用的 MPI 阻塞消息传递例程。

  • MPI_Send
    基本的阻塞发送操作。例程仅在发送任务中的应用程序缓冲区空闲以供重用后返回。请注意,此例程可能会在不同系统上以不同方式实现。 MPI 标准允许使用系统缓冲区,但并不要求它。一些实现实际上可能使用同步发送(下面讨论)来实现基本的阻塞发送。 MPI_Send (&buf,count,datatype,dest,tag,comm) MPI_SEND (buf,count,datatype,dest,tag,comm,ierr)
  • MPI_Recv
    接收消息并阻塞,直到请求的数据在接收任务的应用程序缓冲区中可用。 MPI_Recv (&buf,count,datatype,source,tag,comm,&status) MPI_RECV (buf,count,datatype,source,tag,comm,status,ierr)
  • MPI_Ssend
    同步阻塞发送:发送消息并阻塞,直到发送任务中的应用程序缓冲区空闲以供重用并且目标进程已开始接收消息。 MPI_Ssend (&buf,count,datatype,dest,tag,comm) MPI_SSEND (buf,count,datatype,dest,tag,comm,ierr)
  • MPI_Sendrecv
    在阻止之前发送消息并发布接收。将阻塞,直到发送应用程序缓冲区空闲以供重用,并且直到接收应用程序缓冲区包含接收到的消息。
MPI_Sendrecv (&sendbuf,sendcount,sendtype,dest,sendtag,
...... &recvbuf,recvcount,recvtype,source,recvtag,
...... comm,&status)
MPI_SENDRECV (sendbuf,sendcount,sendtype,dest,sendtag,
...... recvbuf,recvcount,recvtype,source,recvtag,
...... comm,status,ierr)
  • MPI_Wait
  • MPI_Waitany
  • MPI_Waitall
  • MPI_Waitsome
    MPI_Wait 阻塞,直到指定的非阻塞发送或接收操作完成。对于多个非阻塞操作,程序员可以指定任何、全部或部分完成。
MPI_Wait (&request,&status)
MPI_Waitany (count,&array_of_requests,&index,&status)
MPI_Waitall (count,&array_of_requests,&array_of_statuses)
MPI_Waitsome (incount,&array_of_requests,&outcount,
...... &array_of_offsets, &array_of_statuses)
MPI_WAIT(请求,状态,错误)
MPI_WAITANY (count,array_of_requests,index,status,ierr)
MPI_WAITALL(计数,array_of_requests,array_of_statuses,
……呃)
MPI_WAITSOME (incount,array_of_requests,outcount,
...... array_of_offsets, array_of_statuses,ierr)
  • MPI_Probe
    对消息执行阻塞测试。 “通配符” MPI_ANY_SOURCE 和 MPI_ANY_TAG 可用于测试来自任何来源或带有任何标签的消息。对于 C 例程,实际源和标记将在状态结构中作为 status.MPI_SOURCE 和 status.MPI_TAG 返回。对于 Fortran 例程,它们将在整数数组 status(MPI_SOURCE) 和 status(MPI_TAG) 中返回。
MPI_Probe (source,tag,comm,&status)
MPI_PROBE (source,tag,comm,status,ierr)

-MPI_Get_count
返回接收到的数据类型元素的来源、标签和数量。可与阻塞和非阻塞接收操作一起使用。对于 C 例程,实际源和标记将在状态结构中作为 status.MPI_SOURCE 和 status.MPI_TAG 返回。对于 Fortran 例程,它们将在整数数组 status(MPI_SOURCE) 和 status(MPI_TAG) 中返回。

MPI_Get_count (&status,datatype,&count)
MPI_GET_COUNT(状态,数据类型,计数,错误)
  • 例子:阻塞信息传递例程-Fortan
    任务 0 ping 任务 1 并等待返回 ping
program ping
include 'mpif.h'

integer numtasks, rank, dest, source, count, tag, ierr
integer stat(MPI_STATUS_SIZE)   ! required variable for receive routines
character inmsg, outmsg
outmsg = 'x'
tag = 1

call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr)

! task 0 sends to task 1 and waits to receive a return message
if (rank .eq. 0) then
    dest = 1
    source = 1
    call MPI_SEND(outmsg, 1, MPI_CHARACTER, dest, tag, MPI_COMM_WORLD, ierr)
    call MPI_RECV(inmsg, 1, MPI_CHARACTER, source, tag, MPI_COMM_WORLD, stat, ierr)

! task 1 waits for task 0 message then returns a message
else if (rank .eq. 1) then
    dest = 0
    source = 0
    call MPI_RECV(inmsg, 1, MPI_CHARACTER, source, tag, MPI_COMM_WORLD, stat, err)
    call MPI_SEND(outmsg, 1, MPI_CHARACTER, dest, tag, MPI_COMM_WORLD, err)
endif

! query receive Stat variable and print message details
call MPI_GET_COUNT(stat, MPI_CHARACTER, count, ierr)
print *, 'Task ',rank,': Received', count, 'char(s) from task', &
        stat(MPI_SOURCE), 'with tag',stat(MPI_TAG)

call MPI_FINALIZE(ierr)

end

6.4 非阻塞信息传递

  • MPI_ISend
    标识内存中用作发送缓冲区的区域。处理会立即继续,无需等待消息从应用程序缓冲区中复制出来。返回通信请求句柄以处理未决消息状态。在对 MPI_Wait 或 MPI_Test 的后续调用指示非阻塞发送已完成之前,程序不应修改应用程序缓冲区。
MPI_Isend (&buf,count,datatype,dest,tag,comm,&request)
MPI_ISEND (buf,count,datatype,dest,tag,comm,request,ierr)
  • MPI_Irecv
    标识内存中用作接收缓冲区的区域。处理会立即继续,无需等待消息被接收并复制到应用程序缓冲区中。返回通信请求句柄以处理未决消息状态。程序必须使用对 MPI_Wait 或 MPI_Test 的调用来确定非阻塞接收操作何时完成以及请求的消息何时在应用程序缓冲区中可用。
MPI_Irecv (&buf,count,datatype,source,tag,comm,&request)
MPI_IRECV (buf,count,datatype,source,tag,comm,request,ierr)
  • MPI_Issend
    非阻塞同步发送。与 MPI_Isend() 类似,除了 MPI_Wait() 或 MPI_Test() 指示目标进程何时收到消息。
MPI_Issend (&buf,count,datatype,dest,tag,comm,&request)
MPI_ISSEND (buf,count,datatype,dest,tag,comm,request,ierr)
  • MPI_Test
  • MPI_Testany
  • MPI_Testall
  • MPI_Testsome
    MPI_Test 检查指定的非阻塞发送或接收操作的状态。如果操作完成,则“flag”参数返回逻辑真 (1),否则返回逻辑假 (0)。对于多个非阻塞操作,程序员可以指定任何、全部或部分完成。
MPI_Test (&request,&flag,&status)
MPI_Testany (count,&array_of_requests,&index,&flag,&status)
MPI_Testall (count,&array_of_requests,&flag,&array_of_statuses)
MPI_Testsome (incount,&array_of_requests,&outcount,
...... &array_of_offsets, &array_of_statuses)
MPI_TEST (request,flag,status,ierr)
MPI_TESTANY (count,array_of_requests,index,flag,status,ierr)
MPI_TESTALL (count,array_of_requests,flag,array_of_statuses,ierr)
MPI_TESTSOME (incount,array_of_requests,outcount,
...... array_of_offsets, array_of_statuses,ierr)
  • MPI_Iprobe
    对消息执行非阻塞测试。 “通配符” MPI_ANY_SOURCE 和 MPI_ANY_TAG 可用于测试来自任何来源或带有任何标签的消息。如果消息到达,则整数“标志”参数返回逻辑真 (1),否则返回逻辑假 (0)。对于 C 例程,实际源和标记将在状态结构中作为 status.MPI_SOURCE 和 status.MPI_TAG 返回。对于 Fortran 例程,它们将在整数数组 status(MPI_SOURCE) 和 status(MPI_TAG) 中返回。
MPI_Iprobe (source,tag,comm,&flag,&status)
MPI_IPROBE (source,tag,comm,flag,status,ierr)
  • 例子:非阻塞信息传递-Fortran
program ringtopo
include 'mpif.h'

integer numtasks, rank, next, prev, buf(2), tag1, tag2, ierr
integer reqs(4)   ! required variable for non-blocking calls
integer stats(MPI_STATUS_SIZE,4)   ! required variable for WAITALL routine
tag1 = 1
tag2 = 2

call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr)

! determine left and right neighbors
prev = rank - 1
next = rank + 1
if (rank .eq. 0) then
    prev = numtasks - 1
endif
if (rank .eq. numtasks - 1) then
    next = 0
endif

! post non-blocking receives and sends for neighbors
call MPI_IRECV(buf(1), 1, MPI_INTEGER, prev, tag1, MPI_COMM_WORLD, reqs(1), ierr)
call MPI_IRECV(buf(2), 1, MPI_INTEGER, next, tag2, MPI_COMM_WORLD, reqs(2), ierr)

call MPI_ISEND(rank, 1, MPI_INTEGER, prev, tag2, MPI_COMM_WORLD, reqs(3), ierr)
call MPI_ISEND(rank, 1, MPI_INTEGER, next, tag1, MPI_COMM_WORLD, reqs(4), ierr)

    ! do some work while sends/receives progress in background

! wait for all non-blocking operations to complete
call MPI_WAITALL(4, reqs, stats, ierr);

    ! continue - do more work

call MPI_FINALIZE(ierr)

end

7 练习2

尝试编制运行阻塞和非阻塞程序

  • 如果您尚未登录,请登录到 LC 车间集群
  • 使用练习 1 中的“Hello World”MPI 程序,添加 MPI 阻塞点对点例程以发送和接收消息
  • 成功编译你的程序
  • 成功运行您的程序 - 几种不同的方式
  • 用非阻塞发送/接收例程尝试同样的事情
    详见https://hpc-tutorials.llnl.gov/mpi/exercise_2/

8 集体共同例程(Collective Communication Routines)

  • 集体操作的类型
    同步(synchronization),进程等待,直到组的所有成员都到达同步点。
    数据移动(data movement),广播(broadcast)、分散(scatter)、聚集(gather)
    集体计算(减少,reductions),组中的一个成员从其他成员收集数据并对该数据执行操作(最小值、最大值、加法、乘法等)。
  • 范围(scope)
    集体通信例程必须涉及通信器范围内的所有进程。默认情况下,所有进程都是通信器 MPI_COMM_WORLD 中的成员。额外的通讯器可以由程序员定义。有关详细信息,请参阅组和通信器管理例程部分。
    即使通信器中的一项任务不参与,也可能发生意外行为,包括程序失败。
    程序员有责任确保通信器中的所有进程参与任意集体操作。
  • 编程注意事项和限制
    集体通信例程不采用消息标记参数。
    流程子集内的集体操作是通过首先将子集划分为新组,然后将新组附加到新通信器(在组和通信器管理例程部分讨论)来完成的。
    只能与 MPI 预定义数据类型一起使用 - 不能与 MPI 派生数据类型一起使用。
    MPI-2 扩展了大多数集体操作,以允许相互通信器之间的数据移动(此处未涵盖)。
    使用 MPI-3,集体操作可以是阻塞的或非阻塞的。本教程仅涵盖阻塞操作。
  • 集体沟通例程
    (若干函数)

  • 集体沟通示例

program scatter
include 'mpif.h'

integer SIZE
parameter(SIZE=4)
integer numtasks, rank, sendcount, recvcount, source, ierr
real*4 sendbuf(SIZE,SIZE), recvbuf(SIZE)

! Fortran stores this array in column major order, so the
! scatter will actually scatter columns, not rows.
data sendbuf /1.0, 2.0, 3.0, 4.0, &
                5.0, 6.0, 7.0, 8.0, &
                9.0, 10.0, 11.0, 12.0, &
                13.0, 14.0, 15.0, 16.0 /

call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr)

if (numtasks .eq. SIZE) then
    ! define source task and elements to send/receive, then perform collective scatter
    source = 1
    sendcount = SIZE
    recvcount = SIZE
    call MPI_SCATTER(sendbuf, sendcount, MPI_REAL, recvbuf, recvcount, MPI_REAL, &
                    source, MPI_COMM_WORLD, ierr)

    print *, 'rank= ',rank,' Results: ',recvbuf

else
    print *, 'Must specify',SIZE,' processors.  Terminating.'
endif

call MPI_FINALIZE(ierr)

end

9 派生数据类型

MPI除了基本数据类型,还支持用户基于基本数据类型定义派生数据类型。基本数据类型都是连续的,派生数据类型允许指定非连续数据并将其视为连续数据。
MPI提供了以下构造派生数据类型的方法:

  • contiguous(连续化)
  • vector(矢量化)
  • indexed(索引化)
  • struct(结构化)

派生数据类型例程

  • MPI_Type_contiguous
    最简单的构造函数。通过制作现有数据类型的 count 个副本来生成新的数据类型。
MPI_Type_contiguous (count,oldtype,&newtype)
MPI_TYPE_CONTIGUOUS (count,oldtype,newtype,ierr)
  • MPI_Type_vector
  • MPI_Type_hvector
    类似于连续,但允许在位移中有规律的间隙(步幅)。 MPI_Type_hvector 与 MPI_Type_vector 相同,只是步幅以字节为单位指定。
MPI_Type_vector (count,blocklength,stride,oldtype,&newtype)
MPI_TYPE_VECTOR (count,blocklength,stride,oldtype,newtype,ierr)
  • MPI_Type_indexed
  • MPI_Type_hindexed
    输入数据类型的位移数组作为新数据类型的映射提供。 MPI_Type_hindexed 与 MPI_Type_indexed 相同,只是偏移量以字节为单位指定。
MPI_Type_indexed (count,blocklens[],offsets[],old_type,&newtype)
MPI_TYPE_INDEXED (count,blocklens(),offsets(),old_type,newtype,ierr)
  • MPI_Type_struct
    新的数据类型是根据完全定义的组件数据类型映射形成的。
    注意:此函数在 MPI-2.0 中已弃用,并由 MPI-3.0 中的 MPI_Type_create_struct 代替
MPI_Type_struct (count,blocklens[],offsets[],old_types,&newtype)
MPI_TYPE_STRUCT (count,blocklens(),offsets(),old_types,newtype,ierr)
  • MPI_Type_extent
    返回指定数据类型的大小(以字节为单位)。对于需要以字节为单位指定偏移量的 MPI 子例程很有用。
    注意:此函数在 MPI-2.0 中已弃用,并由 MPI-3.0 中的 MPI_Type_get_extent 取代
MPI_Type_extent(数据类型,&extent)
MPI_TYPE_EXTENT(数据类型,范围,错误)
  • MPI_Type_commit
    向系统提交新的数据类型。所有用户构造(派生)数据类型都是必需的。
MPI_Type_commit (&datatype)
MPI_TYPE_COMMIT (datatype,ierr)
  • MPI_Type_free
    解除分配指定的数据类型对象。如果在循环中创建了许多数据类型对象,则使用此例程对于防止内存耗尽尤为重要。
MPI_Type_free (&datatype)
MPI_TYPE_FREE (datatype,ierr)

几种数据类型的例子

  • Contiguous Derived Data Type
  • Vector Derived Data Type
  • Indexed Derived Type
  • Struct Derived Data Type

10 组和通信器管理例程

10.1 组和通信器对比

  • 组是一组有序的进程。组中的每个进程都与唯一的整数等级相关联。等级值从零开始到 N-1,其中 N 是组中的进程数。在 MPI 中,组在系统内存中表示为一个对象。程序员只能通过“句柄”访问它。组始终与通信器对象相关联。
  • 通信器包含一组可以相互通信的进程。所有 MPI 消息都必须指定一个通信器。在最简单的意义上,通信器是一个额外的“标签”,必须包含在 MPI 调用中。像组一样,通信器在系统内存中被表示为对象,程序员只能通过“句柄”访问。例如,包含所有任务的通信器的句柄是 MPI_COMM_WORLD。
  • 从程序员的角度来看,一个组和一个通信器是一体的。组例程主要用于指定应该使用哪些进程来构建通信器。

10.2 Group 和 Communicator 对象的主要用途

  1. 允许您根据功能将任务组织到任务组中。
  2. 跨相关任务的子集启用集体通信操作。
  3. 为实现用户定义的虚拟拓扑提供基础。
  4. 提供安全通信。

10.3 编程注意事项和限制

  • 组/通信器是动态的 - 它们可以在程序执行期间创建和销毁。
  • 进程可能位于多个组/通信器中。他们将在每个组/传播者中拥有独特的排名。
  • MPI 提供了 40 多个与组、通信器和虚拟拓扑相关的例程。

一般用法:

  1. 使用 MPI_Comm_group 从 MPI_COMM_WORLD 中提取全局组的句柄
  2. 使用 MPI_Group_incl 将新组作为全局组的子集
  3. 使用 MPI_Comm_create 为新组创建新的通信器
  4. 使用 MPI_Comm_rank 确定新通信器中的新等级
  5. 使用任何 MPI 消息传递例程进行通信
  6. 完成后,使用 MPI_Comm_free 和 MPI_Group_free 释放新的通信器和组(可选)

10.4 例子

Fortran - 组和通信器示例

program group
include 'mpif.h'

integer NPROCS
parameter(NPROCS=8)
integer rank, new_rank, sendbuf, recvbuf, numtasks
integer ranks1(4), ranks2(4), ierr
integer orig_group, new_group, new_comm   ! required variables
data ranks1 /0, 1, 2, 3/, ranks2 /4, 5, 6, 7/

call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, numtasks, ierr)

if (numtasks .ne. NPROCS) then
    print *, 'Must specify NPROCS= ',NPROCS,' Terminating.'
    call MPI_FINALIZE(ierr)
    stop
endif

sendbuf = rank

! extract the original group handle
call MPI_COMM_GROUP(MPI_COMM_WORLD, orig_group, ierr)

! divide tasks into two distinct groups based upon rank
if (rank .lt. NPROCS/2) then
    call MPI_GROUP_INCL(orig_group, NPROCS/2, ranks1, new_group, ierr)
else
    call MPI_GROUP_INCL(orig_group, NPROCS/2, ranks2, new_group, ierr)
endif

! create new new communicator and then perform collective communications
call MPI_COMM_CREATE(MPI_COMM_WORLD, new_group, new_comm, ierr)
call MPI_ALLREDUCE(sendbuf, recvbuf, 1, MPI_INTEGER, MPI_SUM, new_comm, ierr)

! get rank in new group
call MPI_GROUP_RANK(new_group, new_rank, ierr)
print *, 'rank= ',rank,' newrank= ',new_rank,' recvbuf= ', recvbuf

call MPI_FINALIZE(ierr)
end

11 虚拟拓扑

  • 虚拟拓扑:在 MPI 方面,虚拟拓扑描述了 MPI 过程到几何“形状”的映射/排序。MPI 支持的两种主要拓扑类型是笛卡尔(网格)和图形。MPI 拓扑是虚拟的——并行机的物理结构和进程拓扑之间可能没有关系。虚拟拓扑建立在 MPI 通信器和组之上。它们必须由应用程序开发人员“编程”。
  • 虚拟拓扑的优势:
    1. 便捷性,虚拟拓扑可能对具有特定通信模式的应用程序有用。
    2. 沟通效率,某些硬件体系结构可能会对连续遥远的“节点”之间的通信施加惩罚。特定实现可以基于给定并行机的物理特性优化过程映射。进程到 MPI 虚拟拓扑的映射取决于 MPI 实现,并且可能完全被忽略。

12 MPI-2和MPI-3简述

  • MPI-2
    MPI-2 新功能的关键领域:
    • 动态流程 - 删除 MPI 静态流程模型的扩展。提供在作业启动后创建新进程的例程。
    • 单面通信 - 提供单向通信的例程。包括共享内存操作(put/get)和远程累积操作。
    • 扩展的集体操作 - 允许将集体操作应用于相互通信者
    • 外部接口 - 定义允许开发人员在 MPI 之上分层的例程,例如调试器和分析器。
    • 附加语言绑定 - 描述 C++ 绑定并讨论 Fortran-90 问题。
    • 并行 I/O - 描述 MPI 对并行 I/O 的支持。
  • MPI-3
    • 非阻塞集体操作 - 允许集体中的任务在不阻塞的情况下执行操作,可能会提供性能改进。
    • 新的单边通信操作 - 更好地处理不同的内存模型。
    • Neighborhood Collectives - 使用额外的通信能力扩展分布式图和笛卡尔过程拓扑。
    • Fortran 2008 绑定 - 从 Fortran90 绑定扩展而来
    • MPIT 工具接口 - 允许 MPI 实现向用户公开某些内部变量、计数器和其他状态(最有可能是性能工具)。
    • 匹配的探测 - 修复了 MPI-2 中的一个旧错误,即无法在多线程环境中探测消息。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值