mpi (1)

++mpi基础教程点击++

mpi官方手册

目前两种最重要的并行编程模型是数据并行和消息传递.

对于机群系统一次通信的开销要远远大于一次计算的开销
应尽可能降低通信的次数.

目前主要的mpi实现

  1. mpich
  2. chimp
  3. lam

理论上说MPI所有的通信功能可以用它的6个基本的调用来实现
  1. MPI_Init(&argc, &argv) //初始化MPI执行环境,建立多个MPI进程之间的联系,为后续通信做准备。
  2. MPI_Finalize() //结束MPI执行环境。
  3. MPI_Comm_rank (MPI_COMM_WORLD, &rank); //标识各个MPI进程的,给出调用该函数的进程的进程号
  4. MPI_Comm_size (MPI_COMM_WORLD, &size); //用来标识相应进程组中有多少个进程
  5. MPI_Send(buf,counter,datatype,dest,tag,comm);
buf counter datatype dest tag comm
发送缓冲区的起始地址,可以是数组或结构指针 非负整数,发送的数据个数 发送数据的数据类型 整型,目的的进程号 整型,消息标志 MPI进程组所在的通信域

将发送缓冲区buf中的counter个datatype类型的数据发送到dest目的进程,本次发送的消息标志是tag(用于和MPI_Recv tag 匹配)

6.MPI_Recv(buf,count,datatype,source,tag,comm,status);

buf counter datatype source tag comm status
接收缓冲区的起始地址,可以是数组或结构指针 非负整数,可接收的数据个数 接收数据的数据类型 整型,发送源进程号 整型,消息标志 MPI进程组所在的通信域 返回状态

MPI_Recv(&buf, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
可以用于接收任意源,任意tag的消息.

status是MPI定义的一个数据类型,返回状态变量

                               status
status.MPI_SOURCE status.MPI_TAG status.MPI_error

通过调用就可以得到返回状态中所包含的

  1. 发送数据进程的进程号
  2. 发送数据使用的tag
  3. 本次接收操作返回的错误代码

MPI预定义数据类型

  1. MPI_INT
  2. MPI_CHAR
  3. MPI_SHORT
  4. MPI_LONG
  5. MPI_FLOAT
  6. MPI_DOUBLE
  7. …8. … 9. … 10. …

MPI数据类型匹配

  1. 有数据类型的通信,发送方和接收方均使用相同的数据类型
  2. 无数据类型的通信,发送方和接收方均使用MPI_BYTE作为数据类型
  3. 打包数据的通信,发送方和接收方均使用MPI_PACKED.

MPI_TYPE用于不加修改的传送内存中的二进制值
MPI_PACK用于数据的打包和解包(MPI_UNPACK)

数据转换

  1. 数据类型的转换 mpi严格要求类型匹配,所以不存在数据类型转换问题.
  2. 数据表示的转换

MPI消息

消息=信封+数据
信封:<源/目,标识,通信域>

数据:<起始地址,数据个数,数据类型>

扫描二维码关注公众号,回复: 4576415 查看本文章

MPI_ANY_SOURCE, MPI_ANY_TAG,接收任意源,任意tag消息

一个发送必须指明一个接收,但一个接收可以有多个发送.

MPI通信域

通信域=进程组+通信上下文

进程组=所有参与通信的进程的集合, 编号从0–N.
MPI_Comm comm; //定义一个名为 comm 的通信域.

通信上下文提供一个相对独立的通信区域,不同的消息在不同的上下文中进行传递,不同上下文消息互不干涉. 用户可以定义新的通信域.

MPI时间函数

  1. MPI_Wtime(); 返回用双精度浮点数表示的秒数.
  2. MPI_Wtick(); 返回MPI_Wtime的精度,单位是秒 测试后=0.000001

MPI获取节点名字(机器名字)和mpi版本号

MPI_Get_processor_name(processor_name, &namelen);

processor_name:机器名字
namelen:返回名字的长度

MPI_Get_version(&version, &subversion);

veprintf(“version %d.%d \n”,version, subversion);
rsion,subversion均为int型, version返回mpi大版本 subversion返回小版本号 例如:3.1

判断MPI是否初始化函数

int flag = 0;

MPI_Initialized(&flag);

唯一一个可以用在MPI_Init()之前的MPI函数,功能是判断MPI_Init是否已经执行.

当MPI_Init已经执行时flag会被赋值为 1 .

MPI并行程序的两种基本模式

  1. 对等模式
  2. 主从模式

MPI通信模式

为了应对不同的通信需求

通信模式 发送 接收
标准通信模式 MPI_SEND MPI_RECV
缓存通信模式 MPI_BSEND
同步通信模式 MPI_SSEND
就绪通信模式 MPI_RSEND

MPI并行编程环境搭建配置

link

编译命令
  1. mpicc:编译并链接c MPI程序
  2. mpicxx:编译并链接c++ MPI程序
  3. mpif77:编译并链接fortran77 MPI程序
  4. mpif90:编译并链接fortran90 MPI程序
  5. mpirun
  6. mpiexec
    这些命令在链接时会自动提供MPI需要的库
常用的编译选项

-n 4 //启用四个进程

-machinefile //启用配置文件

node01:1
node02:1
node03:1
node04:1

-p4pg pgfile

阻塞通信
非阻塞通信

非阻塞通信主要用于计算和通信的重叠,从而提高整个程序的执行效率

以上均为点对点通信


组通信

组通信一般实现三个功能

  1. 通信 //数据的传输
  2. 同步 //实现组内所有进程在特定地点上执行进度取得一致
  3. 计算 //对给定的数据完成一定的操作

组通信按通信方向不同可以分为三种:

一对多通信

graph LR
A-->B 
A-->C
A-->D

多对一通信

graph LR
B-->A
C-->A
D-->A

多对多通信

MPI 广播

一对多组通信,将消息广播给组内所有进程,包括它本身在内
MPI_Bcast(buffer,counter,datatype,root,comm);

buf counter datatype root comm
消息缓冲区的起始地址 将广播/接收的数据个数 广播/接收的数据类型 广播数据的跟进程标识号 通信域

image

MPI收集

多对一通信 每个进程包括根进程将消息发送到根进程
根进程将收到的消息依次存放到自己的消息缓存区中
对于收集,每个进程发送的消息不同,但个数必须相同,数据类型也是相同的

根进程指定的接收数据个数是指从每一个进程接受到的数据的个数,而不是总的接收个数.

MPI_Gather(sendbuf, sendcount, datatype, recvbuf, recvcount, recvtype, root, comm);

sendbuf sendcounter datatype recvbuf recvcount recvtype root comm
发送消息缓冲区的起始地址 发送消息的数据个数 发送消息的数据类型 接收消息缓冲区的起始地址 待接收的数据个数 接收数据的数据类型 接收进程的rank,根节点 通信域

其中下面两个仅对根节点(接收进程)有意义

  1. recvcount
  2. recvtype
    image
    MPI_Gatherv() 和MPI_Gather()功能类似,也完成数据的收集功能,区别在于它可以从不同进程接收不同数量的数据.
    对于MPI_Gatherv(); recvcount(待接收数据个数)是一个数组
    MPI_Gatherv(sendbuf, sendcount, sendtype, recvbuf, recvcount,displs, recvtype, root, comm);
    sendbuf: 每个进程该值相同时,每次都从相同的位置取数据,不同时每个进程收集来的数据不同.
    sendcounter: 每个进程该值不同.
    disples: 整数数组,每个数据存放位置相对于recvbuf的位移

image

#include <mpi.h>  
#include <stdio.h>  
#include <stdlib.h>      
int main (int argc, char* argv[])
{
	int rank, size,i,j;
	int namelen;
	char processor_name[MPI_MAX_PROCESSOR_NAME];
	MPI_Init (&argc, &argv);      /* starts MPI*/
	MPI_Status status;
	MPI_Comm_rank (MPI_COMM_WORLD, &rank);        /* get current process id*/
	MPI_Comm_size (MPI_COMM_WORLD, &size);        /* get number of processes*/
	MPI_Get_processor_name(processor_name, &namelen);
	//printf( "进程id为%d 共有 %d个进程 on %s\n\n\n", rank, size,processor_name );
	printf(" 进程号 = %d ,共有%d个进程 , 节点 = %s\n",rank,size,processor_name);
	MPI_Barrier(MPI_COMM_WORLD);//同步	
	int sendarray[100];
	int *rbuf, *displs, *recvcount;
	//int stride=5;
	int sum=0;
	for(i=0;i<=size-1;i++)  sum+=i;
	rbuf=(int*)malloc(size*sum*sizeof(int));
	displs=(int*)malloc(size*sizeof(int));
	recvcount=(int*)malloc(size*sizeof(int));
		
	for(i=0;i<100;i++) sendarray[i]=i+100;
	for(i=0;i<size;i++) recvcount[i] = i*size;
	displs[0] = 0;
	for(i=1;i<size;i++) displs[i]=displs[i-1]+(i-1)*size;

	MPI_Barrier(MPI_COMM_WORLD);//同步	
	//MPI_Gatherv(sendbuf, sendcount, sendtype, recvbuf, recvcount,displs, recvtype, root, comm);
	MPI_Gatherv(sendarray, rank*size, MPI_INT, rbuf,recvcount ,displs, MPI_INT, 0, MPI_COMM_WORLD);
	if(rank==0)
	{															
		for(i=0;i<size*sum;i++) printf("rbuf[%d] = %d\n",i,rbuf[i]);
	}
	MPI_Finalize();   //MPI end.
	return 0;
}

MPI 散发

一对多通信

和广播不同的是root向各个进程发送的数据可以是不同的.
广播是把一个或一组相同的数据广播出去,散发可以同时给通信域中的进程发送不同的数据.

MPI_Scatter(sendbuf, sendcount, sendtype,recvbuf, recvcount, recvtype, root, comm);

sendbuf sendcounter sendtype recvbuf recvcount recvtype root comm
发送消息缓冲区的起始地址 发送到各个进程的数据个数 待散发消息的数据类型 接收消息缓冲区的起始地址 待接收的数据个数 接收数据的数据类型 接收进程的rank,根节点 通信域

sendbuf为要散发数据的起始地址, 根据sendcount和size ,root会把缓冲区数据切成size段,然后依次发送给各个进程.
image

当然还有MPI_Scatterv(); //不详细展开

image

MPI组收集

MPI_Allgather(sendbuf, sendcount, sendtype, recvbuf, recvcount,recvtype,comm);

  1. MPI_GATHER是将数据收集到ROOT进程而MPI_ALLGATHER相当于每一个进程都作为ROOT执行了一次MPI_GATHER调用,即每一个进程都收集到了其它所有进程的数据
  2. MPI_ALLGATHER和MPI_GATHER效果相同,只不过相当与每个进程都执行了一次.

image

MPI_Allgatherv(sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs, recvtype,comm);

MPI全互换

MPI_Alltoall(sendbuf, sendcount, sendtype, recvbuf, recvcount,recvtype, comm);

sendbuf sendcount sendtype recvbuf recvcount recvtype comm
发送消息缓冲区的起始地址 发到每个进程的消息个数 消息的数据类型 接收消息缓冲区的起始地址 从每个进程接收消息的个数 接收消息的数据类型 通信域
  1. 全互换是组内进程之间完全的消息交换
  2. 每一个进程向所有进程发送消息
  3. 每一个进程接收所有进程的消息
  4. mpi_allgather 每个进程发送一个相同的消息给所有进程, mpi_alltoall散发给不同进程的消息是不同的
  5. mpi_alltoall 的每个进程可以向每个接收者发送数目不同的数据
  6. 第 i 个进程发送的第 j 块数据将被第 j 个进程接收并存放在其接收消息缓冲区的 第 i 块
  7. 每个进程和根进程之间,发送的数据量必须和接受的数据量相等

image

image

MPI_Alltoallv();

MPI同步

MPI_Barrier(comm);
阻塞所有的进程知道所有进程都调用了它.

MPI归约

MPI组归约

MPI归约并散发

MPI扫描


具有不连续数据的发送

处理不连续的数据有两种基本方法:

  1. 允许用户自定义新的数据类型(又称派生数据类型)
  2. 数据的打包与解包

派生数据类型

打包和解包

pack ----unpack
MPI_Pack(inbuf, incount, datatype, outbuf, outcount, position, comm );

inbuf incount datatype outbuf outcount position comm
输入缓冲区起始地址 输入数据项个数 每个输入数据项的类型 输出缓冲区开始地址 输出缓冲区大小 缓冲区当前位置 通信域

MPI_Unpack(inbuf, insize, position, outbuf, outcount, datatype, comm );

inbuf insize position outbuf outcount datatype comm
输入缓冲区起始地址 输入数据项个数 缓冲区当前地址 输出缓冲区开始地址 输出缓冲区大小 每个输入数据项类型 通信域

备忘:

经常使用的MPI函数调用方式

  1. MPI_Status status; //状态函数
  2. MPI_Barrier(MPI_COMM_WORLD);//同步
  3. char processor_name[MPI_MAX_PROCESSOR_NAME];
    MPI_Get_processor_name(processor_name, &namelen); //获取节点名称

猜你喜欢

转载自blog.csdn.net/qq_21376807/article/details/85114520
mpi