笔记——MPI简介

什么是MPI

1、MPI是一个库,而不是一门语言。
2、MPI是一种标准或规范的代表,而不特指某一个对它的具体实现。
3、MPI是一种消息传递编程模型,并成为这种编程模型的代表和事实上的标准。MPI虽然很庞大。但是它的最终目的是服务于进程间通信这一目标的。

目前主要的MPI实现

实现名称研制单位网址
MpichArgonne and MSUhttp://www-unix.mcs.anl.gov/mpi/mpich
ChimpEdinburghftp://ftp.epcc.ed.ac.uk/pub/packages/chimp/
LamOhio State Universityhttp://www.mpi.nd.edu/lam/

MPI消息传递过程

1、消息装配:将发送数据从发送缓冲区中取出,加上消息信封等形成一个完整的消息。
2、消息传递:将装配好的消息从发送端传递到接收端。
3、消息拆卸:从接收到的消息中取出数据送入接收缓冲区。

MPI消息的组成

MPI消息包括信封和数据两个部分,信封指出了发送或接收消息的对象及相关信息,而数据是本消息将要传递的内容,信封和数据又分别包括三个部分,可以用一个三元组来表示:
信封 <源/目 标识 通信域>
数据 <起始地址 数据个数 数据类型>

MPI通信域

MPI通信域包括两部分,进程组和通信上下文,进程组即所有参加通信的进程的集合;通信上下文提供一个相对独立的通信区域,不同的消息在不同的上下文中进行传递,不同上下文的消息互不干涉,通信上下文可以将不同的通信区别开来。
一个预定义的通信域MPI_COMM_WORLD由MPI提供,MPI初始化后,便会产生这一描述子,它包括了初始化时可得的全部进程,进程是由它们在MPI_COMM_WORLD组中的进程号所标识。

第一个MPI程序“ Hello World!”

#include "mpi.h"
#include <stdio.h>
#include <math.h>
void main(int argc,char *argv[]){
    int myid , numprocs;
    int namelen;
    char processor_name[MPI_MAX_PROCESSOR_NAME];
    MPI_Init(&argc,&argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&myid);
    MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
    MPI_Get_processor_name(processor_name,&namelen);
    fprintf(stderr,"Hello World! Process %d of %d on %s\n",myid,numprocs,processor_name);
    MPI_Finalize();
}

六个接口构成的MPI子集

MPI初始化

int MPI_Init(int *argc, char ***argv)
MPI_INIT是MPI程序的第一个调用,它完成MPI程序所有的初始化工作,所有MPI程序的第一条可执行语句都是这条语句。

MPI结束

int MPI_Finalize(void)
MPI_FINALIZE是MPI程序的最后一个调用,它结束MPI程序的运行,它是MPI程序的最后一条可执行语句,否则程序的运行结果是不可预知的。

当前进程标识

MPI_COMM_RANK(comm,rank)
IN comm 该进程所在的通信域 句柄
OUT rank 调用进程在comm中的标识号
int MPI_Comm_rank(MPI_Comm comm, int *rank)
返回调用进程在给定的通信域中的进程标识号

通信域包含的进程数

MPI_COMM_SIZE(comm,size)
IN comm 通信域 句柄
OUT size 通信域comm内包括的进程数 整数
int MPI_Comm_size(MPI_Comm comm, int *size)
返回给定的通信域中所包括的进程的个数

消息发送

MPI_SEND(buf,count,datatype,dest,tag,comm)
IN buf 发送缓冲区的起始地址(可选类型)
IN count 将发送的数据的个数(非负整数)
IN datatype 发送数据的数据类型(句柄)
IN dest 目的进程标识号(整型)
IN tag 消息标志(整型)
IN comm 通信域(句柄)
int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)

消息接收

MPI_RECV(buf,count,datatype,source,tag,comm,status)
OUT buf 接收缓冲区的起始地址(可选数据类型)
IN count 最多可接收的数据的个数(整型)
IN datatype 接收数据的数据类型(句柄)
IN source 接收数据的来源即发送数据的进程的进程标识号(整型)
IN tag 消息标识 与相应的发送操作的表示相匹配相同(整型)
IN comm 本进程和发送进程所在的通信域(句柄)
OUT status 返回状态 (状态类型)
int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

举例

#include "mpi.h"
#include <stdio.h>
#include <math.h>
#include <string.h>
void main(int argc,char *argv[]){
    char message[20];
    int myrank;
    MPI_Status status;
    MPI_Init(&argc,&argv);/* MPI程序的初始化*/
    MPI_Comm_rank(MPI_COMM_WORLD,&myrank);/* 得到当前进程的标识*/
    if(myrank==0){/* 若是 0 进程*/
        strcpy(message,"Hello,process 1");
        MPI_Send(message,strlen(message),MPI_CHAR,1,99,MPI_COMM_WORLD);/* 发送数据 */
    }
    else if (myrank==1){/* 若是 1 进程*/
        MPI_Recv(message,20,MPI_CHAR,0,99,MPI_COMM_WORLD, &status);/* 接收数据 */
        printf("received:%s\n",message);
    }
    
    MPI_Finalize();/* MPI程序结束*/
}

用MPI实现计时功能

double MPI_Wtime(void) 

MPI_WTIME返回一个用浮点数表示的秒数, 它表示从过去某一时刻到调用时刻所经历的时间 。(墙上时间)
MPI_WTIME returns a floating-point number of seconds, representing elapsed wall-clock time since some time in the past.
The “time in the past” is guaranteed not to change during the life of the process. The user is responsible for converting large numbers of seconds to other units if they are preferred.
The times returned are local to the node that called them. There is no requirement that different nodes return “the same time.”

double MPI_Wtick()

MPI_WTICK返回MPI_WTIME的精度,单位是秒,可以认为是一个时钟滴答所占用的时间(连续时钟滴答之间的秒数,例如,如果时钟由硬件实现为每毫秒递增的计数器,则 MPI_WTICK 返回的值应为10-3,表示用函数MPI_WTIME 得到的时间精度为干分之一秒。)
辨析:
函数 clock() 记录的是CPU时间(包含用户代码,库函数,系统调用耗时,不包括空闲等待时间),而 MPI_Wtime() 和 GET_TIME()记录的是墙上时间(在 CPU 时间的基础上包括了空闲等待时间)。

//下面的程序测试MPI的时间函数是否正确
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "mpi.h"
int main(int argc ,char **argv){
    int err = 0;
    double t1 , t2;
    double tick;
    int i;
    MPI_Init(&argc,&argv);
    t1=MPI_Wtime();
    t2=MPI_Wtime();
    if(t2-t1>0.1||t2<t1<0.0){
        err++;
        fprintf(stderr,"Two successive calls to MPI_Wtime gave strange results: (%f)(%f)\n",t1,t2);
    }
    for(i=0;i<10;i++){
        t1=MPI_Wtime();
        sleep(1);/* 睡眠1秒钟 */
        t2=MPI_Wtime();
        if(t2-t1>=(1.0-0.01)&&t2-t1<=5.0)
            break;/* 两次计时得到的时间间隔合理,则退出 */
        if(t2-t1>5.0)
            i=9;
        /* 若两次计时得到的时间间隔过大,则改变循环计数变量的值,迫使程序从循环退出 */
    }
    /* 从上知,若计时函数正确,则不需要循环10次程序即从循环退出,否则会重复执行到10次 */
    if(i==10){
        /* 计时函数不正确 */
        fprintf(stderr,"Timer around sleep(1) didi not give 1 second: gave %f\n",t2-t1);
        err++;
    }
    tick=MPI_Wtick();
    printf("tick = %.9f\n",tick);
    /* 得到一个时钟滴答的时间 */
    if(tick>1.0||tick<0.0){
        err++;
        fprintf(stderr,"MPI_Wtime gave a strange result :(%f)\n",tick);
    }
    MPI_Finalize();
}

获取机器的名字和MPI版本号

int MPI_Get_processor_name ( char *name, int *resultlen)
OUT name 当前进程所运行机器的名字 
OUT resultlen 返回名字的的长度(以可打印字符的形式)

调用返回调用进程所在机器的名字

int MPI_Get_version(int * version, int * subversion) 

返回MPI的主版本号version和次版本号subversion

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "mpi.h"
int main(int argc ,char **argv){
    char *name;
    int len;//名称的长度(以字符为单位)
    int version;
    int subversion;
    name=(char *)malloc(sizeof(char)*100);
    MPI_Init(&argc,&argv);
    MPI_Get_processor_name(name,&len);
    printf("name=%s len=%d\n",name,len);
    MPI_Get_version(&version,&subversion);
    printf("version= %d subversion= %d\n",version,subversion);
    MPI_Finalize();
    free(name);
}

是否初始化及错误输出

int MPI_Initialized(int *flag) 
OUT flag MPI_INIT是否已执行标志 ,未执行时flag=0,执行完flag=1

在MPI程序中唯一一个可以用在MPI_INIT之前的MPI调用是MPI_INITALIZED,它的功能就是判断MPI_INIT是否已经执行。

int MPI_Abort(MPI_Comm comm, int errorcode)
IN comm 退出进程所在的通信域 
IN errorcode 返回到所嵌环境的错误码

在编写MPI程序的过程中,若发现已出现无法恢复的严重错误,因而只好退出MPI程序的执行,MPI提供了这样的调用,并且在退出时可以返回给调用环境一个错误码 。

/* 本例子分两种情况 一种是masternode == 0 是缺省的情况 另一种是 masternode != 0
是指定最后一个进程为主进程的情况*/
#include "mpi.h"
#include<stdio.h>
#include<string.h>
int main(int argc , char **argv){
    int node,size ,i;
    int masternode = 0;
    int flag=100;
    MPI_Initialized(&flag);
    //printf("Init是否已经执行 %d\n",flag);//为0
    MPI_Init(&argc,&argv);
    MPI_Initialized(&flag);
    //printf("Init是否已经执行 %d\n",flag);//为1
    MPI_Comm_rank(MPI_COMM_WORLD,&node);
    MPI_Comm_size(MPI_COMM_WORLD,&size);
    //printf("argc=%d\n",argc);
    for(i=0;i<argc;i++){
        fprintf(stderr,"myid= %d, procs= %d, argv[%d]=%s\n",node, size, i, argv[i]);
        if(argv[i]&&strcmp("lastmaster" , argv[i])==0){
            masternode = size -1;
            /* 将最后一个进程设置为master */
        }
    }
    if(node == masternode){
        /* 由master进程执行退出操作 */
        fprintf(stderr, "myid=%d is masternode Abort!\n",node);
        MPI_Abort(MPI_COMM_WORLD,99);
    }
    else{
        /* 非master进程等待 */
        fprintf(stderr,"myid= %d is not masternode Barrier!\n",node);
        MPI_Barrier(MPI_COMM_WORLD);
        //用于一个通信子中所有进程的同步
    }
    MPI_Finalize();
    
    /*测试举例
    mpicc XXX.c -o XXX 
  	mpirun -n 10 ./XXX lastmaster   
  	int argc , char **argv 分别为函数运行时传入的参数个数和参数列表  argv[0]=./mpi_init_abort  argv[1]=lastmaster*/
}

数据接力传送

#include"mpi.h"
#include<stdio.h>
int main(int argc, char **argv){
    int rank, value, size;
    MPI_Status status;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&rank);
    MPI_Comm_size(MPI_COMM_WORLD,&size);
    do{
        if(rank==0){
            fprintf(stderr,"\nPlease gave new value=");
            /* 进程0读入要传递的数据 */
            scanf("%d" ,&value);
            fprintf(stderr,"%d read <-<- (%d)\n",rank,value);
            if(size>1){
                MPI_Send(&value,1,MPI_INT,rank+1,0,MPI_COMM_WORLD);
                fprintf(stderr,"%d send (%d)->-> %d\n",rank,value,rank+1);
                /* 若不少于一个进程,则向下一个进程传递该数据 */
            }
        }
        else{
            MPI_Recv(&value,1,MPI_INT,rank-1,0,MPI_COMM_WORLD,&status);
            fprintf(stderr,"%d receive (%d)<-<- %d\n",rank,value,rank-1);
            if(rank<size-1){
                MPI_Send(&value,1,MPI_INT,rank+1,0,MPI_COMM_WORLD);
                fprintf(stderr,"%d send (%d)->-> %d\n",rank,value,rank+1);
                /* 若当前进程不是最后一个进程,则将该数据继续向后传递 */
            }
        }
         MPI_Barrier(MPI_COMM_WORLD);
        /* 执行一下同步,加入它主要是为了前后两次数据传递分开 */
    }while (value>=0);
    MPI_Finalize();
}

在这里插入图片描述

任意进程间相互问候

//任意进程都向其它的进程问好
#include "mpi.h"
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char **argv){
    int me, option, namelen, size;
    int buffer[2],node;
    MPI_Status status;
    char processor_name[MPI_MAX_PROCESSOR_NAME];
    /*MPI_MAX_PROCESSOR_NAME为MPI预定义的宏,代表MPI的具体实现中允许机器名字的最大长度*/
    MPI_Init(&argc,&argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&me);
    MPI_Comm_size(MPI_COMM_WORLD,&size);
    if(size<2){//至少三个进程间问候
        fprintf(stderr,"systest requires at least 2 processes");
        MPI_Abort(MPI_COMM_WORLD,1);
    }
    MPI_Get_processor_name(processor_name,&namelen);
    fprintf(stderr,"PRocess %d is alive on %s\n",me,processor_name);
    MPI_Barrier(MPI_COMM_WORLD);/* 同步 */
    if(me==0){
        /* 进程0负责打印提示信息 */
        printf("\nHello test from all to all\n");
    }
    for(node=0;node<size;node++){
        /* 循环对每一个进程进行问候 */
        if(node!=me){
            buffer[0]=me;
            buffer[1]=node;
            MPI_Send(buffer,2,MPI_INT,node,1,MPI_COMM_WORLD);/* 首先将问候信息发出 */
            MPI_Recv(buffer,2,MPI_INT,node,1,MPI_COMM_WORLD,&status);/* 然后接收被问候进程对自己发送的问候信息 */
            if((buffer[0]!=node)||(buffer[1]!=me)){
                /* 若接收到的消息内容不是问候自己的或者不是被问候方的身份问候自己,则出错 */
                fprintf(stderr,"Hello:%d!=%d or %d!=%d\n",buffer[0],node,buffer[1],me);
                printf("Mismatch on hello process ids; node = %d\n",node);
            }
            printf("Hello from %d to %d\n",me ,node);
        }
    }
    MPI_Finalize();
}

在这里插入图片描述

任意源和任意标识的使用

在接收操作中,通过使用任意源和任意tag标识,使得该接收操作可以接收任何进程以任何标识发送给本进程的数据,但是该消息的数据类型必须和接收操作的数据类型相一致。

#include "mpi.h"
#include <stdio.h>
int main(int argc, char **argv){
    int rank, size, i ,buf=0;
    MPI_Status status ;
    MPI_Init(&argc,&argv);
    MPI_Comm_rank(MPI_COMM_WORLD,&rank);
    MPI_Comm_size(MPI_COMM_WORLD,&size);
    if(rank==0){
        for(i=0;i<5*(size-1);i++){
            MPI_Recv(&buf,1,MPI_INT,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status);
            printf("Msg=%d from %d with tag %d\n",buf,status.MPI_SOURCE,status.MPI_TAG);
        }
    }
    else{
        for(i=0;i<5;i++){
            buf=rank++;
            MPI_Send(&buf,1,MPI_INT,0,i,MPI_COMM_WORLD);
        }
    }
    MPI_Finalize();
}

在这里插入图片描述

编写安全的MPI程序

编写MPI程序,如果通信调用的顺序使用的不当,很容易造成死锁。即当两个进程需要相互交换数据时,一定要将它们的发送和接收操作按照次序进行匹配,即一个进程的发送操作在前,接收操作在后,而另一个进程的接收操作在前,发送操作在后,前后两个发送和接收操作要相互匹配,保证消息安全传递。
在这里插入图片描述

  • 19
    点赞
  • 130
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MPI(Message Passing Interface)是一种用于并行计算的编程模型和库。归并排序是一种经典的排序算法,适合并行计算。 在MPI中,可以通过发送和接收消息来实现进程间的通信。下面是一个基于MPI的归并排序的伪代码: ```python def parallel_merge_sort(data): # 获取进程总数和当前进程编号 size = MPI.COMM_WORLD.Get_size() rank = MPI.COMM_WORLD.Get_rank() # 计算每个进程要处理的数据量 chunk_size = len(data) // size remainder = len(data) % size # 将数据分发到各个进程 if rank == 0: for i in range(size): if i < remainder: chunk = data[i * (chunk_size + 1):(i + 1) * (chunk_size + 1)] else: chunk = data[remainder + i * chunk_size:remainder + (i + 1) * chunk_size] MPI.COMM_WORLD.send(chunk, dest=i, tag=0) # 接收数据 chunk = MPI.COMM_WORLD.recv(source=0, tag=0) # 对本地数据进行排序 chunk.sort() # 归并排序 for step in range(size): # 计算要交换数据的进程编号 partner = (rank + step) % size # 发送和接收数据 sendbuf = chunk recvbuf = MPI.COMM_WORLD.recv(source=partner, tag=step) if rank < partner: sendtag = step recvtag = step + size else: sendtag = step + size recvtag = step MPI.COMM_WORLD.send(sendbuf, dest=partner, tag=sendtag) chunk = merge(chunk, recvbuf) # 将排序好的数据返回 if rank == 0: result = [] for i in range(size): chunk = MPI.COMM_WORLD.recv(source=i, tag=size) result.extend(chunk) return result else: MPI.COMM_WORLD.send(chunk, dest=0, tag=size) ``` 在这个算法中,首先将原始数据分发到各个进程,然后每个进程对本地数据进行排序,接着对每个步骤进行归并排序,并且使用MPI的send和recv函数进行交换数据。最后将排序好的数据返回到主进程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值