MPI教程7-9

目录

7.点对点通信例程

7.1一般概念

7.1.1 首先,一个简单的例子

7.1.2点对点操作的类型:

7.1.3秩序和公平:

7.2 MPI消息传递例程参数

7.3阻塞消息传递例程

7.3.1常用函数

7.3.2示例:阻塞消息传递例程 

7.4非阻塞消息传递例程

7.4.1非阻塞点对点通信常用函数

7.4.2例子:非阻塞点对点通信例程

8.练习2

9.集体通信例程

9.1基本概念

9.1.1集体行动的类型:

 9.1.2范围:

9.1.3编程注意事项和限制:

9.2常用函数

9.3例子

 参考文献


7.点对点通信例程

7.1一般概念

7.1.1 首先,一个简单的例子

PI的值可以用各种方法计算。考虑蒙特卡洛逼近PI的方法:

在边长为2r的正方形上刻一个半径为r的圆

圆的面积为∏r2,正方形的面积为4r2

圆面积与正方形面积之比为∏r2⁄4r2=∏/4

如果在正方形内随机生成N个点,则这些点(M)中约有N*∏⁄4</sub></var>应落在圆内。

则∏近似为:

N*π⁄4=M

π⁄4=M/N

π=4*M/N

请注意,增加生成的点数可以提高近似值。

 此过程的串行伪代码

npoints = 10000
circle_count = 0

do j = 1,npoints
  generate 2 random numbers between 0 and 1
  xcoordinate = random1
  ycoordinate = random2
  if (xcoordinate, ycoordinate) inside circle
  then circle_count = circle_count + 1
end do

PI = 4.0*circle_count/npoints

导致了一个“令人尴尬的并行”解决方案:

它将循环迭代分解为块,每个块可由不同任务同时执行。

每个任务都会多次执行其循环部分。

每个任务都可以在不需要其他任务提供任何信息的情况下完成其工作(不存在数据依赖关系)。

主任务使用发送/接收点对点操作从其他任务接收结果。

伪代码解决方案:粗体突出显示并行性的更改。

npoints = 10000
circle_count = 0

p = number of tasks
num = npoints/p

find out if I am MASTER or WORKER

do j = 1,num
  generate 2 random numbers between 0 and 1
  xcoordinate = random1
  ycoordinate = random2
  if (xcoordinate, ycoordinate) inside circle
  then circle_count = circle_count + 1
end do


if I am MASTER
  receive from WORKERS their circle_counts
  compute PI (use MASTER and WORKER calculations)
else if I am WORKER
  send to MASTER circle_count
endif

关键概念:在可用任务之间划分工作,通过点对点消息传递调用进行数据通信。

7.1.2点对点操作的类型:

MPI点对点操作通常涉及两个(且仅两个)不同MPI任务之间的消息传递。一个任务正在执行发送操作,另一个任务正在执行匹配的接收操作。

有不同类型的发送和接收例程用于不同的目的。例如:

  • 同步发送
  • 阻塞发送/阻塞接收
  • 非阻塞发送/非阻塞接收
  • 缓冲发送
  • 组合发送/接收
  • “准备好”发送

任何类型的发送例程都可以与任何类型的接收例程配对。

MPI还提供了几个与发送-接收操作相关的例程,例如用于等待消息到达或探测消息是否已到达的例程。

缓冲

在一个完美的世界中,每个发送操作都会与其匹配的接收操作完全同步。这种情况很少发生。无论如何,MPI实现必须能够在两个任务不同步时存储数据。

考虑以下两种情况:

发送操作在接收准备就绪前5秒发生-接收挂起时消息在哪里?

多个发送到达同一接收任务,该任务一次只能接受一个发送-正在“备份”的消息会发生什么情况?

MPI实现(而不是MPI标准)决定了在这些情况下数据会发生什么。通常,保留一个系统缓冲区以保存传输中的数据。例如: 

 系统缓冲区空间为:  

  • 对程序员来说是不透明的,完全由MPI库管理
  • 一种容易耗尽的有限资源
  • 通常是神秘的,没有很好的记录
  • 能够存在于发送端、接收端或发送端和接收端同时存在
  • 可以提高程序性能的东西,因为它允许发送-接收操作是异步的。

用户管理的地址空间(即程序变量)称为应用程序缓冲区。MPI还提供用户管理的发送缓冲区。

阻塞与非阻塞

 大多数MPI点到点例程可以在阻塞或非阻塞模式下使用。

阻塞:

只有在安全地修改应用程序缓冲区(您的发送数据)以供重用后,阻塞发送例程才会“返回”。安全意味着修改不会影响用于接收任务的数据。Safe并不意味着数据是实际接收到的——它很可能位于系统缓冲区中。

阻塞发送可以是同步的,这意味着与接收任务发生握手以确认安全发送。

如果使用系统缓冲区保存数据以最终传递到接收端,则阻塞发送可以是异步的。

阻塞接收仅在数据到达并准备好供程序使用后“返回”。

非阻塞:

非阻塞发送和接收例程的行为类似——它们几乎会立即返回。它们不等待任何通信事件完成,例如消息从用户内存复制到系统缓冲区空间或消息的实际到达。

非阻塞操作只是“请求”MPI库在其能够执行操作时执行该操作。用户无法预测何时会发生这种情况。

在知道请求的非阻塞操作实际上是由库执行之前,修改应用程序缓冲区(变量空间)是不安全的。有一些“等待”例程用于执行此操作。

非阻塞通信主要用于将计算与通信重叠,并利用可能的性能增益。

7.1.3秩序和公平:

秩序:

MPI保证消息不会相互超越。

如果发送方连续向同一目的地发送两条消息(消息1和消息2),并且两条消息都匹配相同的接收,则接收操作将在消息2之前接收消息1。

如果一个接收者连续发送两次接收(接收1和接收2),并且都在寻找相同的消息,则接收1将在接收2之前接收消息。

如果有多个线程参与通信操作,则秩序规则不适用。

公平:

MPI不能保证公平性——防止“操作饥饿”取决于程序员。

示例:任务0向任务2发送消息。但是,任务1发送与任务2接收相匹配的竞争消息。只有一个发送将完成。

7.2 MPI消息传递例程参数

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

Buffer 

指向要发送或接收的数据的程序(应用程序)地址空间。在大多数情况下,这只是要发送/接收的变量名。对于C程序,此参数是通过引用传递的,并且通常必须在其前面加上一个去地址符号:&var1

Data Count

指示要发送的特定类型数据元素个数。

Data Type

出于可移植性的原因,MPI预定义了它的基本数据类型。下表列出了本标准要求的类型。

笔记:

  • 程序员也可以创建自己的数据类型(参见派生数据类型)。
  • MPI_字节和MPI_压缩与标准C或Fortran类型不对应。
  • 如果可能,建议使用灰色字体显示的类型。
  • 一些实现可能包括附加的基本数据类型(MPI_LOGICAL2、MPI_COMPLEX32等)。检查MPI头文件。

Destination

用于发送例程的参数,该参数指示传递消息应在的进程。指定为接收进程的rank。

Source

用于接收例程的参数,该参数指示消息的原始进程。指定为发送进程的rank。这可以设置为通配符MPI_ANY_SOURCE以接收来自任何任务的消息。

Tag

程序员指定的任意非负整数,用于唯一标识消息。发送和接收操作应与消息标记匹配。对于接收操作,通配符MPI_ANY_TAG可用于接收任何消息,而不考虑其标记。MPI标准保证整数0-32767可以用作标记,但大多数实现允许的范围要大得多。

Communicator

对于Source或Destination字段有效的通信上下文或进程集。除非程序员明确创建新的通讯器,否则通常使用预定义的通讯器MPI_COMM_WORLD。

Status

对于接收操作,指示消息的源和消息的标记。在C语言中,此参数是指向预定义结构MPI_Status(例如stat.MPI_SOURCE stat.MPI_TAG)的指针。在Fortran中,它是大小为MPI_STATUS_size(例如stat(MPI_Source)stat(MPI_TAT))的整数数组。此外,可通过MPI_Get_count例程从Status获取接收的实际字节数。如果稍后将查询消息的源、标记或大小,则可以替换常量MPI_STATUS_IGNORE和MPI_STATUS_IGNORE。

Request

由非阻塞发送和接收操作使用。由于非阻塞操作可能在获得请求的系统缓冲区空间之前返回,因此系统发出唯一的“请求号”。程序员稍后(在等待类型例程中)使用系统分配的“句柄”来确定非阻塞操作的完成。在C语言中,此参数是指向预定义结构 MPI_Request的指针。在Fortran中,它是一个整数。

7.3阻塞消息传递例程

7.3.1常用函数

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

MPI_Send

MPI_Recv

MPI_Ssend

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 (&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 (request,status,ierr)
MPI_WAITANY (count,array_of_requests,index,status,ierr)
MPI_WAITALL (count,array_of_requests,array_of_statuses,
...... ierr)
MPI_WAITSOME (incount,array_of_requests,outcount,
...... array_of_offsets, array_of_statuses,ierr)

 MPI_Probe

MPI_Probe (source,tag,comm,&status)
MPI_PROBE (source,tag,comm,status,ierr)

MPI_Get_count

MPI_Get_count (&status,datatype,&count)
MPI_GET_COUNT (status,datatype,count,ierr)

7.3.2示例:阻塞消息传递例程 

任务0 pings任务1并等待返回ping

c语言代码

#include "mpi.h"
#include <stdio.h>

main(int argc, char *argv[])  {
int numtasks, rank, dest, source, rc, count, tag=1;
char inmsg, outmsg='x';
MPI_Status Stat;   // required variable for receive routines

MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

// task 0 sends to task 1 and waits to receive a return message
if (rank == 0) {
    dest = 1;
    source = 1;
    MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
    MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
    }

// task 1 waits for task 0 message then returns a message
else if (rank == 1) {
    dest = 0;
    source = 0;
    MPI_Recv(&inmsg, 1, MPI_CHAR, source, tag, MPI_COMM_WORLD, &Stat);
    MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
    }

// query receive Stat variable and print message details
MPI_Get_count(&Stat, MPI_CHAR, &count);
printf("Task %d: Received %d char(s) from task %d with tag %d \n",
        rank, count, Stat.MPI_SOURCE, Stat.MPI_TAG);

MPI_Finalize();
}

7.4非阻塞消息传递例程

7.4.1非阻塞点对点通信常用函数



MPI_Isend

MPI_Isend (&buf,count,datatype,dest,tag,comm,&request)
MPI_ISEND (buf,count,datatype,dest,tag,comm,request,ierr)

MPI_Irecv

MPI_Irecv (&buf,count,datatype,source,tag,comm,&request)
MPI_IRECV (buf,count,datatype,source,tag,comm,request,ierr)

MPI_Issend 

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 (&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_Iprobe (source,tag,comm,&flag,&status)
MPI_IPROBE (source,tag,comm,flag,status,ierr)

7.4.2例子:非阻塞点对点通信例程

环形拓扑中的最近邻交换

 

C语言-非阻塞消息传递示例

#include "mpi.h"
#include <stdio.h>

main(int argc, char *argv[])  {
int numtasks, rank, next, prev, buf[2], tag1=1, tag2=2;
MPI_Request reqs[4];   // required variable for non-blocking calls
MPI_Status stats[4];   // required variable for Waitall routine

MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

// determine left and right neighbors
prev = rank-1;
next = rank+1;
if (rank == 0)  prev = numtasks - 1;
if (rank == (numtasks - 1))  next = 0;

// post non-blocking receives and sends for neighbors
MPI_Irecv(&buf[0], 1, MPI_INT, prev, tag1, MPI_COMM_WORLD, &reqs[0]);
MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]);

MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]);
MPI_Isend(&rank, 1, MPI_INT, next, tag1, MPI_COMM_WORLD, &reqs[3]);

    // do some work while sends/receives progress in background

// wait for all non-blocking operations to complete
MPI_Waitall(4, reqs, stats);

    // continue - do more work

MPI_Finalize();
}

8.练习2

9.集体通信例程

9.1基本概念

scatter:分散
reduction:压缩
gather:聚集
broadcast:广播

9.1.1集体行动的类型:

  • 同步-进程等待组中的所有成员到达同步点。
  • 数据移动-广播、分散/收集、多对多。
  • 集体计算(压缩reduction)-组中的一个成员从其他成员收集数据,并对该数据执行操作(最小、最大、加法、乘法等)。

 9.1.2范围:

集体通信例行程序必须涉及通信器范围内的所有过程。默认情况下,所有进程都是communicator MPI_COMM_WORLD中的成员。其他通讯器可由程序员定义。有关详细信息,请参阅组和通信器管理例程部分。

即使通信器中的一个任务不参与,也可能发生意外行为,包括程序失败。

程序员有责任确保通信器中的所有进程都参与任何集体操作。

9.1.3编程注意事项和限制:

集体通信例程不接受消息标记参数。

进程子集内的集体操作通过以下方式完成:首先将子集划分为新组,然后将新组附加到新的通讯器上(在“组和通讯器管理例程”一节中讨论)。

只能与MPI预定义的数据类型一起使用,而不能与MPI派生的数据类型一起使用。

MPI-2扩展了大多数集合操作,以允许内部通信设备之间的数据移动(此处未涉及)。

使用MPI-3,集合操作可以是阻塞或非阻塞。本教程仅介绍阻塞操作。

9.2常用函数

MPI_Barrier

同步操作。在组中创建屏障同步。当到达MPI_Barrier调用时,每个任务都会阻塞,直到组中的所有任务到达相同的MPI_Barrier调用。然后,所有任务都可以自由进行。

MPI_Barrier (comm)
MPI_BARRIER (comm,ierr)

MPI_Bcast

数据移动操作。将一条消息从排名为“root”的进程广播(发送)到组中的所有其他进程。

MPI_Bcast (&buffer,count,datatype,root,comm)
MPI_BCAST (buffer,count,datatype,root,comm,ierr)

MPI_Scatter

数据移动操作。将来自单个源任务的不同消息分发给组中的每个任务。

MPI_Scatter (&sendbuf,sendcnt,sendtype,&recvbuf,recvcnt,recvtype,root,comm)
MPI_SCATTER (sendbuf,sendcnt,sendtype,recvbuf,recvcnt,recvtype,root,comm,ierr)

MPI_Gather

数据移动操作。将组中每个任务的不同消息收集到单个目标任务。此例程是MPI_Scatter的反向操作。

MPI_Gather (&sendbuf,sendcnt,sendtype,&recvbuf,recvcount,recvtype,root,comm)
MPI_GATHER (sendbuf,sendcnt,sendtype,recvbuf,recvcount,recvtype,root,comm,ierr)

MPI_Allgather

数据移动操作。将数据连接到组中的所有任务。实际上,组中的每个任务都在组内执行一对所有广播操作。

MPI_Allgather (&sendbuf,sendcount,sendtype,&recvbuf,recvcount,recvtype,comm)
MPI_ALLGATHER (sendbuf,sendcount,sendtype,recvbuf,recvcount,recvtype,comm,info)

MPI_Reduce

集体计算操作。对组中的所有任务应用缩减操作,并将结果放入一个任务中

MPI_Reduce (&sendbuf,&recvbuf,count,datatype,op,root,comm)
MPI_REDUCE (sendbuf,recvbuf,count,datatype,op,root,comm,ierr)

预定义的MPI缩减操作如下所示。用户还可以使用MPI_Op_create例程定义自己的缩减函数。

MPI_Reduce手册页中的注释:始终假定该操作是关联的。所有预定义的操作也被假定为可交换的。用户可以定义假定为关联但不可交换的操作。还原的“规范”评估顺序由组中进程的等级决定。然而,实现可以利用结合性,或者结合性和交换性来改变求值顺序。这可能会改变非严格关联和交换操作(如浮点加法)的缩减结果。[给实施者的建议]强烈建议实施MPI_REDUCE,以便在函数以相同顺序应用于相同参数时获得相同的结果。请注意,这可能会阻止利用处理器的物理位置进行优化。[给实施者的建议结束] 

 MPI_Allreduce

集体计算操作+数据移动。应用缩减操作并将结果放入组中的所有任务中。这相当于MPI_Reduce后跟MPI_Bcast

MPI_Allreduce (&sendbuf,&recvbuf,count,datatype,op,comm)
MPI_ALLREDUCE (sendbuf,recvbuf,count,datatype,op,comm,ierr)

MPI_Reduce_scatter

集体计算操作+数据移动。首先,对组中所有任务的向量进行元素级缩减。接下来,结果向量被分割成不相交的部分,并分布在任务中。这相当于在MPI_Reduce之后执行MPI_Scatter操作。

MPI_Reduce_scatter (&sendbuf,&recvbuf,recvcount,datatype,op,comm)
MPI_REDUCE_SCATTER (sendbuf,recvbuf,recvcount,datatype,op,comm,ierr)

 MPI_Alltoall

数据移动操作。组中的每个任务执行分散操作,按索引顺序向组中的所有任务发送不同的消息

MPI_Alltoall (&sendbuf,sendcount,sendtype,&recvbuf,recvcnt,recvtype,comm)
MPI_ALLTOALL (sendbuf,sendcount,sendtype,recvbuf,recvcnt,recvtype,comm,ierr)

MPI_Scan

针对任务组中的缩减操作执行扫描操作。

MPI_Scan (&sendbuf,&recvbuf,count,datatype,op,comm)
MPI_SCAN (sendbuf,recvbuf,count,datatype,op,comm,ierr)

9.3例子

c语言

#include "mpi.h"
#include <stdio.h>
#define SIZE 4

main(int argc, char *argv[])  {
int numtasks, rank, sendcount, recvcount, source;
float sendbuf[SIZE][SIZE] = {
    {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}  };
float recvbuf[SIZE];

MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);

if (numtasks == SIZE) {
    // define source task and elements to send/receive, then perform collective scatter
    source = 1;
    sendcount = SIZE;
    recvcount = SIZE;
    MPI_Scatter(sendbuf,sendcount,MPI_FLOAT,recvbuf,recvcount,
                MPI_FLOAT,source,MPI_COMM_WORLD);

    printf("rank= %d  Results: %f %f %f %f\n",rank,recvbuf[0],
        recvbuf[1],recvbuf[2],recvbuf[3]);
    }
else
    printf("Must specify %d processors. Terminating.\n",SIZE);

MPI_Finalize();
}

示例程序输出:

rank= 0  Results: 1.000000 2.000000 3.000000 4.000000
rank= 1  Results: 5.000000 6.000000 7.000000 8.000000
rank= 2  Results: 9.000000 10.000000 11.000000 12.000000
rank= 3  Results: 13.000000 14.000000 15.000000 16.000000

 参考文献

Message Passing Interface (MPI) | LLNL HPC Tutorials

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haimianjie2012

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值