本篇文章只是记录api的用法和回顾,方便记忆
MPI
- 在程序里的任何执行点,只能连接一个用户提供的缓冲区。
简单例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>
const int MAX_STRING= 100;
int main()
{
char greeting[MAX_STRING];
int comm_sz;
int my_rank;
int q;
MPI_Init(NULL,NULL);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
if(my_rank != 0)
{
sprintf(greeting,"Greetings from process %d of %d!",my_rank,comm_sz);
MPI_Send(greeting,strlen(greeting)+1,MPI_CHAR,0,0,MPI_COMM_WORLD);
}
else
{
printf("Greetings from process %d of %d !\n",my_rank,comm_sz);
for(q=1;q<comm_sz;q++)
{
MPI_Recv(greeting,MAX_STRING,MPI_CHAR,q,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
printf("%s\n",greeting);
}
}
MPI_Finalize();
return 0;
}
#编译
mpicc -g -Wall -o mpi_hello mpi_hello.c
#-g :产生供gdb调试用的可执行文件
# http://www.jianshu.com/p/30ffc01380a0
#-Wall:编译后显示所有警告
#-o:输出到指定文件
MPI_Init
告知MPI系统进行所有必要的初始化设置。例如,系统可能需要为消息缓冲区分配存储空间,为进程指定进程号等。
PS:在调用MPI_Init前不应该调用其他MPI函数
#返回一个int型错误码(大部分时候忽略)
int MPI_Init(
#指向argc的指针,不使用设置为NULL
int * argc_p /*in/out*/,
#指向argv的指针,不使用设置为NULL
char *** argv_p /*in/out*/ )
MPI_Finalize
告知MPI系统MPI已经所使用完毕,为MPI而分配的所有资源都可以释放
int MPI_Finalize(void)
Sample 1:
...
#include <mpi.h>
...
int main(int argc,char * argv[]){
MPI_Init(&argc,&argv);
...
MPI_Finalize();
return 0;
}
MPI_COMM_WORLD
通信子 是一组可以互相发送消息的进程集合。MPI_Init的其中一个目的,是在用户启动程序时,定义由用户启动的所有进程组成的通信子。
PS:一个通信子的进程所发的消息不能被另一个通信子中的进程所接收
int MPI_Comm_size(
#通信子
MPI_Comm comm /*in*/,
#返回通信子的进程号
int* comm_sz_p /*out*/)
int MPI_Comm_rank(
#通信子
MPI_Comm comm /*in*/,
#返回正在调用进程在通信子的进程号
int* my_rank_p /*out*/)
发送模式和缓冲发送
1.MPI为发送操作提供了四种模式:标准(standard),同步(synchronous),就绪(ready)和缓冲(buffered)。
2.不同的模式下,MPI实现决定是将消息的内容复制到自己的存储空间,还是一直阻塞到一个相匹配的接收操作被提交。
MPI_Send 标准模式
进程执行发送。
MPI标准允许以两种不同的方式来实现 :
1.简单地将消息复制到MPI设置的缓冲区并返回。
2.直到对应的MPI_Recv出现前都阻塞。
此外,许多MPI函数都设置了使系统从缓冲到阻塞间切换的阀值,即相对较小的消息就交由MPI_Send缓冲,但对于大型数据就选择阻塞模式。
MPI提供的缓冲机制不安全。
int MPI_Send(
#指向包含消息的内存块的指针,如greeting
void* msg_buf_p /*in*/,
#指定要发送的数据量,如strlen(greeting)+1
int msg_size /*in*/,
#数据类型MPI
MPI_Datatype msg_type /*in*/,
#指定要接收消息的进程号
int dest /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的
int tag /*in*/,
#通信子
MPI_Comm commuicator /*in*/)
MPI数据类型 | C语言数据类型 |
---|---|
MPI_CHAR | signed char |
MPI_SHORT | signed short int |
MPI_INT | signed int |
MPI_LONG | signed long int |
MPI_LONG_LONG | signed long long int |
MPI_UNSIGNED_CHAR | unsigned char |
MPI_UNSIGNED_SHORT | unsigned short int |
MPI_UNSIGNED | unsigned int |
MPI_UNSIGNED | unsigned long int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_LONG_DOUBLE | long double |
MPI_BYTE | |
MPI_PACKED |
%MPI_Ssend 同步模式
1.发送操作会一直阻塞到一个相匹配的接收操作被提交。
int MPI_Ssend(
#指向包含消息的内存块的指针,如greeting
void* msg_buf_p /*in*/,
#指定要发送的数据量,如strlen(greeting)+1
int msg_size /*in*/,
#数据类型MPI
MPI_Datatype msg_type /*in*/,
#指定要接收消息的进程号
int dest /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的
int tag /*in*/,
#通信子
MPI_Comm commuicator /*in*/)
%MPI_Rsend 就绪模式
1.在发送操作前会有一个相匹配的接收操作被提交,否则发送操作就是错误的。
int MPI_Rsend(
#指向包含消息的内存块的指针,如greeting
void* msg_buf_p /*in*/,
#指定要发送的数据量,如strlen(greeting)+1
int msg_size /*in*/,
#数据类型MPI
MPI_Datatype msg_type /*in*/,
#指定要接收消息的进程号
int dest /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的
int tag /*in*/,
#通信子
MPI_Comm commuicator /*in*/)
%MPI_Bsend 缓冲模式
1.如果一个相关匹配的接收操作还没有被提交,那么MPI必须复制消息到本地存储空间。本地存储空间必须由用户程序提供。
int MPI_Bsend(
#指向包含消息的内存块的指针,如greeting
void* msg_buf_p /*in*/,
#指定要发送的数据量,如strlen(greeting)+1
int msg_size /*in*/,
#数据类型MPI
MPI_Datatype msg_type /*in*/,
#指定要接收消息的进程号
int dest /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的
int tag /*in*/,
#通信子
MPI_Comm commuicator /*in*/)
%MPI_Buffer_attach
函数MPI_Bsend使用的缓冲区必须通过调用该函数来指定。
int MPI_Buffer_attach(
#指向用户程序分配的一块内存空间的指针
void* buffer /*in*/,
#这块空间以字节为单位的大小
int buffer_size /*in*/)
%MPI_Buffer_detach
- 函数MPI_Bsend使用的缓冲区必须通过调用该函数来指定。
- 调用MPI_Buffer_detach会阻塞直到buffer中的消息被传送完。
int MPI_Buffer_detach(
#返回的时之前分配的块的内存的地址
void* buf_p /*out*/,
#内存块的大小
int* buffer_size_p /*out*/)
%MPI_Pack_size
1.被传送的数据需要的存储空间大小,可以通过调用MPI_Pack_size来计算。
2.输出的参数给出了一条消息所需空间大小的上界。但仍然不够,一条消息还需要存储包括目的地,标志,通信子等信息,所以对于每条消息,都需要多余的开销。MPI针对这些增加的开销给出了上界常数:MPI_BSEND_OVERHEAD。
int MPI_Pack_size(
#元素个数
int count /*in*/,
#元素类型
MPI_Datatype datatype /*in*/,
#通信子
MPI_Comm comm /*in*/,
#计算的缓存区大小
int* size_p /*out*/,
Sample 11:
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#define SIZE 6
int main(int argc,char* argv[])
{
int my_rank;
int comm_sz;
int src=0;
int dest=1;
int i,bsize,tsize;
int buffer[SIZE];
char* tmpbuffer;
int* tmpbuf;
int* recv_b;
MPI_Init(NULL,NULL);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
if(my_rank == src)
{
for(i=0;i<SIZE;i++)
buffer[i]=i;
MPI_Pack_size(SIZE,MPI_INT,MPI_COMM_WORLD,&bsize);
tmpbuffer =(char*) malloc(bsize+MPI_BSEND_OVERHEAD);
MPI_Buffer_attach(tmpbuffer,bsize+MPI_BSEND_OVERHEAD);
for(dest=1;dest<comm_sz;dest++)
{
MPI_Bsend(buffer,SIZE,MPI_INT,dest,2000,MPI_COMM_WORLD);
buffer[dest]=buffer[dest]*2;
}
MPI_Buffer_detach(&tmpbuf,&tsize);
}
else
{
/*
if(my_rank==1)
{
int index=0;
while(index<3e8)
{
index++;
}
}*/
recv_b=malloc(SIZE*sizeof(int));
MPI_Recv(recv_b,SIZE,MPI_INT,src,2000,MPI_COMM_WORLD, MPI_STATUS_IGNORE);
//printf("%d ------ oook\n",my_rank);
for(i=0;i<SIZE;i++)
printf("%d -> %d\n",my_rank,recv_b[i]);
printf("----------->\n");
}
MPI_Finalize();
return 0;
}
MPI_Recv
进程接收消息
int MPI_Recv(
#指向内存块
void* msg_buf_p /*out*/,
#指定内存块要存储对象的数据量
int buf_size /*in*/,
#对象的类型
MPI_Datatype buf_type /*in*/,
#接受源
int source /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的,要与发送的消息相匹配
int tag /*in*/,
#通信子
MPI_Comm commuicator /*in*/,
#特殊MPI常量MPI_STATUS_IGNORE
MPI_Status* status_p /*out*/)
MPI_Sendrecv
调用一次这个函数,分别执行一次阻塞式消息发送和一次消息接收。它的有用之处,实现了通信调度,使程序不再挂起和崩溃。
int MPI_Sendrecv(
#指向包含消息的内存块的指针
void* send_buf_p /*in*/,
#指定要发送的数据量
int send_buf_size /*in*/,
#数据类型MPI
MPI_Datatype send_buf_type /*in*/,
#指定要接收消息的进程号(可以与source相同也可以不相同,一般相同,代表向源发送,再向源接收)
int dest /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的
int send_tag /*in*/,
#指向内存块
void* recv_buf_p /*out*/,
#指定内存块要存储对象的数据量
int recv_buf_size /*in*/,
#对象的类型
MPI_Datatype recv_buf_type /*in*/,
#接受源(可以与dest相同也可以不相同,一般相同,代表向源发送,再向源接收)
int source /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的,要与发送的消息相匹配
int recv_tag /*in*/,
#通信子
MPI_Comm commuicator /*in*/,
#特殊MPI常量MPI_STATUS_IGNORE
MPI_Status* status_p /*in*/)
MPI_Sendrecv_replace
如果发送和接收使用的是同一个缓冲区。
int MPI_Sendrecv_replace(
#指向包含消息的内存块的指针
void* buf_p /*in/out*/,
#指定要发送/接收的数据量
int buf_size /*in*/,
#数据类型MPI
MPI_Datatype buf_type /*in*/,
#指定要接收消息的进程号(可以与source相同也可以不相同,一般相同,代表向源发送,再向源接收)
int dest /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的,要与发送的消息相匹配
int send_tag /*in*/,
#指定要接收消息的进程号(可以与dest相同也可以不相同,一般相同,代表向源发送,再向源接收)
int source /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的,要与发送的消息相匹配
int recv_tag /*in*/,
#通信子
MPI_Comm commuicator /*in*/,
#特殊MPI常量MPI_STATUS_IGNORE
MPI_Status* status_p /*in*/)
%MPI_Status用法
在不知道以下信息的情况下接收消息
1)消息的数据量
2)消息的发送者
3)消息的标签
MPI结构
typedef struct MPI_Status {
int count;
int cancelled;
int MPI_SOURCE;
int MPI_TAG;
int MPI_ERROR;
} MPI_Status;
#接收到的数据量不是存储在应用程序可以直接访问到的域中,但用户可以调用MPI_Get_count函数找回这个值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>
const int MAX_STRING= 100;
int main()
{
char greeting[MAX_STRING];
int comm_sz;
int my_rank;
int q;
int buffer_size=0;
MPI_Status status;
MPI_Init(NULL,NULL);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
if(my_rank != 0)
{
sprintf(greeting,"Greetings from process %d of %d!",my_rank,comm_sz);
MPI_Send(greeting,strlen(greeting)+1,MPI_CHAR,0,0,MPI_COMM_WORLD);
}
else
{
printf("Greetings from process %d of %d !\n",my_rank,comm_sz);
for(q=1;q<comm_sz;q++)
{
//对消息进行锁定测试(不接收消息)
/*
int MPI_Probe(
#源进程序号
int source,
#消息标识
int tag,
#通信子
MPI_Comm comm,
#状态(返回的)
MPI_Status *status)
*/
MPI_Probe(q,0,MPI_COMM_WORLD,&status);
MPI_Get_count(&status, MPI_CHAR, &buffer_size);
MPI_Recv(greeting,buffer_size,MPI_CHAR,q,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
printf("%s\n",greeting);
}
}
MPI_Finalize();
return 0;
}
集合通信和点对点通信多方面不同
- 在通信子中 所有进程必须调用相同的集合通信函数 。
- 每个进程传递给MPI集合函数必须是 “相容的” 。
- 参数output_data_p只用在dest_process上。然而,所有进程仍需要传递一个与outputdata_p相对应的实际参数,即使它的值只是NULL。
- 点对点通信函数通过标签和通信子匹配。
MPI_Reduce
树形结构通信
PS:可用于N维度向量
int MPI_Reduce(
#发送缓冲区地址
void* input_data_p /*in*/,
#接收缓冲区地址
void* output_data_p /*out*/,
#发送缓冲区内元素数
int count /*in*/,
#发送元素的数据类型
MPI_Datatype datatype /*in*/,
#规约操作
MPI_Op operator /*in*/,
#存放结果的根进程号
int dest_process /*in*/,
#通信子
MPI_Comm comm /*in*/)
运算符值 | 含义 |
---|---|
MPI_MAX | 求最大值 |
MPI_MIN | 求最小值 |
MPI_SUM | 求累加和 |
MPI_PROD | 求累乘积 |
MPI_LAND | 逻辑与 |
MPI_BAND | 按位与 |
MPI_LOR | 逻辑或 |
MPI_BOR | 按位或 |
MPI_LXOR | 逻辑异或 |
MPI_BXOR | 按位异或 |
MPI_MAXLOC | 求最大值和最小值所在的位置 |
MPI_MINLOC | 求最小值和最大值所在的位置 |
MPI_Allreduce
蝶形结构通信
int MPI_Allreduce(
#发送缓冲区地址
void* input_data_p /*in*/,
#接收缓冲区地址
void* output_data_p /*out*/,
#发送缓冲区内元素数
int count /*in*/,
#发送元素的数据类型
MPI_Datatype datatype /*in*/,
#规约操作
MPI_Op operator /*in*/,
#通信子
MPI_Comm comm /*in*/)
MPI_Bcast
树形结构广播
- 阻塞式
int MPI_Bcast(
#接收缓冲区(已装载)
void* data_p /*in/out*/,
#缓冲区内最大项数
int count /*in*/,
#项的数据类型
MPI_Datatype datatype /*in*/,
#根进程序号
int source /*in*/,
#通信子
MPI_Comm comm /*in*/)
MPI_Scatter
散射
int MPI_Scatter(
#发送缓冲区
void* send_buf_p /*in*/,
#发送到每个进程的数据量(一般和recv_count相同)
int send_count /*in*/,
#发送元素的数据类型
MPI_Datatype send_type /*in*/,
#接收缓冲区(已装载)
void* recv_buf_p /*out*/,
#每次接收的元素数
int recv_count /*in*/,
#接收元素的数据类型
MPI_Datatype recv_type /*in*/,
#接收进程序号
int src_proc /*in*/,
#通信子
MPI_Comm comm /*in*/)
%MPI_Scatterv
不等长散射
int MPI_Scatterv(
#发送的内存地址
void* sendbuf /*in*/,
#发送sendcount[q]是发送q的sendtype类型的对象的数量
int* sendcounts /*in*/,
#偏移量的起始位置sendbuf+displacements[q]
int* displacements /*in*/,
#数据类型
MPI_Datatype sendtype /*in*/,
#接收的数据缓存块
void* recvbuf /*out*/,
#接收的个数
int recvcount /*in*/,
#发送根目录
int root /*in*/,
#通信子
MPI_Comm comm /*in*/)
MPI_Gather
聚集
int MPI_Gather(
#发送缓冲区
void* send_buf_p /*in*/,
#发送缓冲区内元素数
int send_count /*in*/,
#发送元素的数据类型
MPI_Datatype send_type /*in*/,
#接收缓冲区(已装载)
void* recv_buf_p /*out*/,
#从每个进程中接收到的数据量(一般和send_count相同)
int recv_count /*in*/,
#接收元素的数据类型
MPI_Datatype recv_type /*in*/,
#接收进程序号
int dest_proc /*in*/,
#通信子
MPI_Comm comm /*in*/)
%MPI_Gatherv
不等长聚集
int MPI_Gatherv(
#发送的内存地址
void* sendbuf /*in*/,
#发送对应内存的元素个数
int sendcounts /*in*/,
#数据类型
MPI_Datatype sendtype /*in*/,
#接收的数据缓存块
void* recvbuf /*out*/,
#接收recvcounts[q]是发送q的sendtype类型的对象的数量
int* recvcounts /*in*/,
#偏移量的起始位置recvbuf+displacements[q]
int* displacements /*in*/,
#接收数据的根目录
int root /*in*/,
#通信子
MPI_Comm comm /*in*/)
MPI_Allgather
全局聚集
int MPI_Allgather(
#发送缓冲区
void* send_buf_p /*in*/,
#发送缓冲区内元素数
int send_count /*in*/,
#发送元素的数据类型
MPI_Datatype send_type /*in*/,
#接收缓冲区(已装载)
void* recv_buf_p /*out*/,
#从每个进程中接收到的数据量(一般和send_count相同)
int recv_count /*in*/,
#接收元素的数据类型
MPI_Datatype recv_type /*in*/,
#通信子
MPI_Comm comm /*in*/)
MPI_派生数据类型
http://www.exuyi.com/course/MPI/content/chapter13/section1/part01/index1.htm
主要思想:如果发送数据的函数知道数据项的类型以及在内存中数据项集合的相对位置,就可以在数据项被发送出去之前将数据项聚集起来。类似地,接收数据的函数可以在数据项被接收后将数据分发到它们的内存中的正确的目标地址中。
正式地,一个派生数据类型是由一系列的MPI基本数据类型和每个数据类型的偏移所组成的。
变量 | 地址 |
---|---|
a | 24 |
b | 40 |
n | 48 |
以a为起点偏移量为0,b:40-24=16,n:48-24=24
{(MPI_DOUBLE,0),(MPI_DOUBLE,16),(MPI_INT,24)}
MPI_Get_address
int MPI_Get_address(
#所指向的内存单元地址
void* location_p /*in*/,
#特殊类型MPI_Aint,长度足以表示系统地址
MPI_Aint* address_p /*out*/)
Sample 2:
MPI_Aint a_addr,b_ddr,n_addr;;
MPI_Get_address(&a,&a_addr);
array_of_displacements[0]=0;
MPI_Get_address(&b,&b_addr);
array_of_displacements[1]=b_addr-a_addr;
MPI_Get_address(&n,&n_addr);
array_of_displacements[2]=n_addr-a_addr;
MPI_Type_create_struct
创建由不同基本数据类型的元素所组成的派生数据类型
int MPI_Type_create_struct(
#数据类型中元素个数(例:double,double,int = 3)
int count /*in*/,
#每个数据项的元素个数(可以是数组)(例:{1,1,1})
int array_of_blocklengths[] /*in*/,
#每个数据项距离消息起始位置的偏移量(例:{0,16,24})
MPI_Aint array_of_displacements[] /*in*/,
#每个数据项的类型(例:{MPI_DOUBLE,MPI_DOUBLE,MPI_INT})
MPI_Datatype array_of_types[] /*in*/,
#自定义数据类型出口(例:MPI_Datatype input_mpi_t)
MPI_Datatype* new_type_p /*out*/)
MPI_Type_commit
允许MPI实现为了在通信函数内使用这一数据类型(例:input_mpi_t)
int MPI_Type_commit(
#新构建的数据类型(例:MPI_Datatype input_mpi_t)
MPI_Datatype* new_mpi_t_p /*in/out*/)
MPI_Type_free
当我们使用新的数据类型时,可以调用一个函数去释放额外的存储空间。
int MPI_Type_free(
#已构建的数据类型(例:MPI_Datatype input_mpi_t)
MPI_Datatype* old_mpi_t_p /*in/out*/)
Sample 3:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>
#define NELEM 5
typedef struct
{
char pt;
double pos[3];
double orientation[3][3];
}Particle;
int main()
{
int comm_sz;
int my_rank;
int source=0;
int tag=1;
int i,j,k;
MPI_Status stat;
Particle p[NELEM],particles[NELEM];
MPI_Datatype particletype;
#项目类型
MPI_Datatype oldtype[3]={MPI_CHAR,MPI_DOUBLE,MPI_DOUBLE};
#个数
int blockcount[3]={1,3,9};
#偏移
MPI_Aint offset[3];
MPI_Init(NULL,NULL);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Address(&(particles[0].pt),&offset[0]);
MPI_Address(particles[0].pos,&offset[1]);
MPI_Address(particles[0].orientation,&offset[2]);
for(i=2;i>=0;i--)
offset[i]-=offset[0];
MPI_Type_struct(3,blockcount,offset,oldtype,&particletype);
MPI_Type_commit(&particletype);
if(my_rank == 0)
{
for(i=0;i<NELEM;i++)
{
particles[i].pt = 'H';
particles[i].pos[0] = 0.0;
particles[i].pos[1] = 1.0;
particles[i].pos[2] = 2.0;
for(j=0;j<3;j++)
for(k=0;k<3;k++)
particles[i].orientation[j][k]=0.99;
}
for(i=1;i<comm_sz;i++)
{
MPI_Send(particles,NELEM,particletype,i,tag,MPI_COMM_WORLD);
}
}
else
{
MPI_Recv(p,NELEM,particletype,source,tag,MPI_COMM_WORLD,&stat);
printf("%d \n",p[2].pt);
printf("%f %f %f \n",p[2].pMPI_Type_struct(5,blockcount,offset,oldtype,&particletype);os[0],p[2].pos[1],p[2].pos[2]);
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
printf("%f ",p[2].orientation[i][j]);
}
printf("\n");
}
printf("\n");
}
MPI_Finalize();
return 0;
}
MPI_Type_create_resized
创建一个能在数据类型上缓冲的新属性类型
int MPI_Type_create_resized(
#旧数据类型
MPI_Datatype oldtype,
#下限
MPI_Aint lb,
#宽度
MPI_Aint extent,
#新类型
MPI_Datatype *newtype)
Sample 9:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>
int my_rank;
int comm_sz;
MPI_Comm comm;
void Get_args(int argc, char* argv[], int* m_p, int* iters_p);
MPI_Datatype Build_cyclic_type(int n, int local_n);
void Print_loc_array(double x[], int n);
int main(int argc,char* argv[])
{
int m,iters;
int n,local_n,i;
double* local_x;
double* x;
MPI_Datatype cyclic_mpi_t;
double start,finish,my_elapsed,elapsed;
MPI_Init(&argc, &argv);
comm = MPI_COMM_WORLD;
MPI_Comm_size(comm, &comm_sz);
MPI_Comm_rank(comm, &my_rank);
Get_args(argc, argv, &m, &iters);
local_n = m ;
n = m*comm_sz;
local_x = malloc(local_n * sizeof(double));
x = malloc(5*n*sizeof(double));
for(i=0;i<local_n;i++)
local_x[i] = my_rank+1;
cyclic_mpi_t = Build_cyclic_type(n, local_n);
MPI_Barrier(comm);
start = MPI_Wtime();
for(i=0;i<iters;i++)
{
MPI_Allgather(local_x,local_n,MPI_DOUBLE,x,local_n,MPI_DOUBLE,comm);
if (my_rank == 0)
{
Print_loc_array(x, n);
memset(x, '0', sizeof(double)*n);
}
}
finish = MPI_Wtime();
my_elapsed = finish - start;
MPI_Reduce(&my_elapsed, &elapsed, 1, MPI_DOUBLE, MPI_MAX, 0, comm);
if (my_rank == 0)
printf("Average elapsed time for block Allgather = %e seconds\n", elapsed/iters);
MPI_Barrier(comm);
start = MPI_Wtime();
for (i = 0; i < iters; i++)
{
MPI_Allgather(local_x, local_n, MPI_DOUBLE,x, 1, cyclic_mpi_t, comm);
if (my_rank == 0)
{
Print_loc_array(x, 5*n);
memset(x, '0', sizeof(double)*n);
}
}
finish = MPI_Wtime();
my_elapsed = finish-start;
MPI_Reduce(&my_elapsed, &elapsed, 1, MPI_DOUBLE, MPI_MAX, 0, comm);
if (my_rank == 0)
printf("Average elapsed time for cyclic Allgather = %e seconds\n",elapsed/iters);
MPI_Finalize();
return 0;
}
void Get_args(int argc, char* argv[], int* m_p, int* iters_p)
{
if(my_rank ==0)
{
*m_p = strtol(argv[1],NULL,10);
*iters_p = strtol(argv[2],NULL,10);
}
MPI_Bcast(m_p, 1, MPI_INT, 0, comm);
MPI_Bcast(iters_p, 1, MPI_INT, 0, comm);
}
MPI_Datatype Build_cyclic_type(int n, int local_n)
{
MPI_Datatype temp_mpi_t, temp1_mpi_t;
MPI_Aint lb, extent;
MPI_Type_vector(local_n, 1, comm_sz, MPI_DOUBLE, &temp_mpi_t);
MPI_Type_get_extent(MPI_DOUBLE, &lb, &extent);
MPI_Type_create_resized(temp_mpi_t, lb,2*extent, &temp1_mpi_t);
MPI_Type_commit(&temp1_mpi_t);
return temp1_mpi_t;
}
void Print_loc_array(double x[], int n)
{
int j;
printf("x = ");
for (j = 0; j < n; j++)
printf("%2.0f ", x[j]);
printf("\n");
fflush(stdout);
}
MPI 程序性能评估
时钟计时
MPI_Wtime()
#返回从过去某一个时刻开始所经过的秒数,墙上时钟
double MPI_Wtime(void)
Sample 4:
double start,finish;
...
start = MPI_Wtime();
...
finish = MPI_Wtime();
printf("%e \n",finish-start);
MPI_Barrier
能够确保同一通信子所有进程都完成调用该函数之前,没有进程能够提前返回。
#通信子
int MPI_Barrier(MPI_Comm comm /*int*/)
Sample 5:
double local_start,local_finish,local_elapsed,elapsed;
...
MPI_Barrier(comm);
local_start = MPI_Wtime();
...
local_finish = MPI_Wtime();
local_elapsed = local_finish - local_start;
MPI_Reduce(&local_elapsed,&elapsed,1,MPI_DOUBLE,MPI_MAX,0,comm);
if(my_rank == 0)
printf("%e \n",elapsed);
MPI_Scan
计算前缀和,分配到各个节点
int MPI_Scan(
#发送缓冲区地址
void* sendbuf_p /*int*/,
#接收缓冲区地址
void* recvbuf_p /*out*/,
#sendbuf_p和recvbuf_p都应该指向有count个datatype类型元素的数据块
int count /*int*/,
#发送元素的数据类型
MPI_Datatype datatype /*int*/,
#规约操作
MPI_Op op /*int*/,
#通信子
MPI_Comm comm /*int*/)
Sample 6:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>
int main()
{
int comm_sz;
int my_rank;
int source=0;
int tag=1;
double local_num;
double sum;
MPI_Init(NULL,NULL);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
local_num = my_rank;
printf("%d ----> %f \n",my_rank,local_num);
MPI_Scan(&local_num,&sum,1,MPI_DOUBLE,MPI_SUM,MPI_COMM_WORLD);
printf("%d ans----> %f\n",my_rank,sum);
MPI_Finalize();
return 0;
}
%MPI_Type_contiguous
从数组中收集邻接元素,然后创建派生数据类型
int MPI_Type_contiguous(
#复制旧数据类型次数
int count /*int*/,
#原来数据类型
MPI_Datatype old_mpi_t /*int*/,
#新数据类型
MPI_Datatype* new_mpi_t_p /*out*/)
Sample 7:
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
int main(void)
{
int x[4];
int i,j;
int comm_sz;
int my_rank;
MPI_Datatype new_type;
int index=0;
for(i=0;i<4;i++)
{
x[i]=(++index);
//y[i]=-1; 一旦初始化便会出错。原因未知
}
MPI_Init(NULL,NULL);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Type_contiguous(4,MPI_INT,&new_type);
MPI_Type_commit(&new_type);
if(my_rank == 0)
{
MPI_Send(x,1,new_type,1,0,MPI_COMM_WORLD);
}
else if(my_rank == 1)
{
int* y=malloc(4*sizeof(int));
MPI_Recv(y,4,MPI_INT,0,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
for(i=0;i<4;i++)
printf("%d \n",y[i]);
}
MPI_Finalize();
return 0;
}
%MPI_Type_vector
可以用来将数组中的数据块组合起来构建派生数据类型,这些块大小相同,在数组中的间隔是等距的。
int MPI_Type_vector(
#块的个数
int count /*in*/,
#块的大小
int blocklength /*in*/,
#块与块的偏移量
int stride /*in*/,
#旧MPI数据类型
MPI_Datatype old_mpi_t /*in*/,
#新MPI数据类型
MPI_Datatype* new_mpi_t_p /*out*/)
Sample 8:
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
int main(void)
{
int x[18];
int y[6];
int i,j;
int comm_sz;
int my_rank;
MPI_Datatype new_type;
int index=0;
for(i=0;i<16;i++)
{
x[i]=(++index);
//y[i]=-1; 一旦初始化便会出错。原因未知
}
MPI_Init(NULL,NULL);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Type_vector(3,2,6,MPI_INT,&new_type);
MPI_Type_commit(&new_type);
if(my_rank == 0)
{
MPI_Send(x,1,new_type,1,0,MPI_COMM_WORLD);
}
else if(my_rank == 1)
{
//int* y=malloc(6*sizeof(int));
MPI_Recv(y,6,MPI_INT,0,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
for(i=0;i<6;i++)
printf("%d \n",y[i]);
}
MPI_Finalize();
return 0;
}
%MPI_Type_indexed
建立取自任意数组元素的派生数据类型。
int MPI_Type_indexed(
#数据类型中元素个数
int count; /*in*/,
#每个数据项的元素个数(可以是数组)
int array_of_blocklengths[] /*in*/,
#每个数据项距离消息起始位置的偏移量
int array_of_displacements /*in*/,
#旧数据类型
MPI_Datatype old_mpi_t /*in*/,
#自定义数据类型出口
MPI_Datatype* new_type_p /*out*/)
Sample 9:
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
int main(void)
{
int x[16];
int i,j;
int comm_sz;
int my_rank;
MPI_Datatype new_type;
int* array_of_block_lens;
int* array_of_disps;
array_of_block_lens = malloc(4*sizeof(int));
array_of_disps = malloc(4*sizeof(int));
int index=0;
for(i=0;i<16;i++)
{
x[i]=(++index);
}
for( i=0;i<4;i++)
{
array_of_block_lens[i] = 4-i;
array_of_disps[i]=i*(4+1);
}
MPI_Init(NULL,NULL);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
MPI_Type_indexed(4,array_of_block_lens,array_of_disps,MPI_INT,&new_type);
MPI_Type_commit(&new_type);
if(my_rank == 0)
{
MPI_Send(x,1,new_type,1,0,MPI_COMM_WORLD);
}
else if(my_rank == 1)
{
int* y=malloc(10*sizeof(int));
MPI_Recv(y,10,MPI_INT,0,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE);
for(i=0;i<10;i++)
printf("%d \n",y[i]);
}
MPI_Finalize();
return 0;
}
%MPI_Pack
每次复制一块要发送的数据到用户提供的缓冲区,该缓冲区既可以接收数据或者发送数据。
int MPI_Pack(
#放入存储区的内存地址
void* in_buf /*in*/,
#放入存储区的个数
int in_buf_count /*in*/,
#元素的数据类型
MPI_Datatype datatype /*in*/,
#放入的数据包
void* pack_buf /*in*/,
#数据包大小
int pack_buf_sz /*in*/,
#关键参数,当调用MPI_Pack时,该参数指向pack_buf中第一个可访问元素的位置
int* position_p /*in/out*/,
#通信子
MPI_Comm comm /*int*/)
%MPI_Unpack
将缓冲区的数据解包
PS:除了进程0以外,其他进程都可以用MPI_Unpack来解包数据
int MPI_Unpack(
#数据包内存地址
void* pack_buf /*in*/,
#数据包大小
int pack_buf_sz /*in*/,
#关键参数,当调用MPI_Unpack时,该参数指向pack_buf中第一个可访问元素的位置
int* position_p /*in/out*/,
#放入数据的内存地址
void* out_buf /*in*/,
#放入数据的内存大小
int out_buf_count /*in*/,
#元素的数据类型
MPI_Datatype datatype /*in*/,
#通信子
MPI_Comm comm /*int*/)
Sample 10:
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
const int PACK_BUF_SZ=100;
int main(void)
{
char pack_buf[PACK_BUF_SZ];
int position = 0;
int i,j;
int comm_sz;
int my_rank;
double a;
double b;
int n;
MPI_Init(NULL,NULL);
MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
if(my_rank == 0)
{
a=1.0;
b=2.0;
n=3;
MPI_Pack(&a, 1, MPI_DOUBLE, pack_buf, PACK_BUF_SZ, &position, MPI_COMM_WORLD);
MPI_Pack(&b, 1, MPI_DOUBLE, pack_buf, PACK_BUF_SZ, &position, MPI_COMM_WORLD);
MPI_Pack(&n, 1, MPI_INT, pack_buf, PACK_BUF_SZ, &position, MPI_COMM_WORLD);
}
MPI_Bcast(pack_buf, PACK_BUF_SZ, MPI_PACKED, 0, MPI_COMM_WORLD);
if (my_rank > 0)
{
MPI_Unpack(pack_buf, PACK_BUF_SZ, &position, &a, 1, MPI_DOUBLE,MPI_COMM_WORLD);
MPI_Unpack(pack_buf, PACK_BUF_SZ, &position, &b, 1, MPI_DOUBLE,MPI_COMM_WORLD);
MPI_Unpack(pack_buf, PACK_BUF_SZ, &position, &n, 1, MPI_INT,MPI_COMM_WORLD);
}
printf("Proc %d > a = %.2f, b = %.2f, n = %d\n", my_rank, a, b, n);
MPI_Finalize();
return 0;
}
%MPI_Iprobe
只测试是否有消息可用,不会真地接收消息。
int MPI_Iprobe(
#来源进程,可以选用 MPI_ANY_SOURCE
int source /*in*/,
#tag:0用于表示消息要打印的,为1表示消息用于计算的。可以选用 MPI_ANY_TAG
int tag /*in*/,
#通信子
MPI Comm comm /*in*/,
#标记为tag的消息是否可用,可以则为true。否则为false
int* msg_avail_p /*out*/,
#特殊MPI常量,例如将status_p->MPI_SOURCE赋予接收到消息来源的线程号。
MPI_Status* status_p /*out*/)