MPI简单入门学习笔记

4.5.1MPI介绍安装

MPI 消息传递接口(Message Passing Interface,简称 MPI)是一种编程接 口标准,而不是一种具体的编程语言。

与多线程、OpenMP 并行程序不同,MPI 是一种基于消息传递的并行编程技术。 MPI 编程是基于消息传递的并行编程技术,是如今应用最为广泛的并行程序开发方法。

MPI实现:MPICH INTELMPI

MPI安装:sudo apt install mpich

4.5.2MPI 程序特点

MPI 程序是基于消息传递的并行程序。消息传递指的是并行执行的各个进程具有自己独 立的堆栈和代码段,作为互不相关的多个程序独立执行,进程之间的信息交互完全通过显式 地调用通信函数来完成。

这与多线程、OpenMP 程序共享同一内存空间有着显著的不同,而 且有利有弊。好处在于带来了更多的灵活性,除了支持多核并行、SMP 并行之外,消息传 递更容易实现多个节点间的并行处理;而另一方面,也正是源于这种进程独立性和显式消息 传递的特点,MPI 标准更加繁复,基于其开发并行程序也更加复杂。 基于消息传递的并行程序可以划分为单程序多数据(Single Program Multiple Data,简 称 SPMD)和多程序多数据 MPMD 两种形式。SPMD 使用一个程序来处理多个不同的数据 集以达到并行的目的。并行执行的不同程序实例处于完全对等的位置。相应的,MPMD 程 序使用不同的程序处理多个数据集,合作求解同一个问题。 SPMD 是 MPI 程序中最常用的并行模型。下图为 SPMD 执行模型的示意图,其中表 示了一个典型的 SPMD 程序,同样的程序 prog_a 运行在不同的处理核上,处理了不同的数 据集。

 

MPI六个基本函数:

1.MPI_Init

任何MPI程序都应该首先调用该函数。 此函数不必深究,只需在MPI程序开始时调用即可(必须保证程序中第一个调用的MPI函数是这个函数)。

MPI_Init(&argc, &argv) //C++ & C

2.MPI_Finalize() //C++

任何MPI程序结束时,都需要调用该函数。切记Fortran在调用MPI_Finalize的时候,需要加个参数ierr来接收返回的值,否则计算结果可能会出问题甚至编译报错。

3.MPI_COMM_RANK

MPI_Comm_Rank(MPI_Comm comm, int *rank)

该函数是获得当前进程的进程标识,如进程0在执行该函数时,可以获得返回值0。可以看出该函数接口有两个参数,前者为进程所在的通信域,后者为返回的进程号。通信域可以理解为给进程分组,比如有0-5这六个进程。可以通过定义通信域,来将比如[0,1,5]这三个进程分为一组,这样就可以针对该组进行“组”操作,比如规约之类的操作。这类概念会在之后的MPI进阶一章中讲解。MPI_COMM_WORLD是MPI已经预定义好的通信域,是一个包含所有进程的通信域,目前只需要用该通信域即可。

在调用该函数时,需要先定义一个整型变量如myid,不需要赋值。将该变量传入函数中,会将该进程号存入myid变量中并返回。

4.MPI_COMM_SIZE

该函数是获取该通信域内的总进程数,如果通信域为MP_COMM_WORLD,即获取总进程数,使用方法和MPI_COMM_RANK相近。

MPI_COMM_SIZE(comm, size) int MPI_Comm_Size(MPI_Comm, int *size)

5.MPI_SEND

该函数为发送函数,用于进程间发送消息,如进程0计算得到的结果A,需要传给进程1,就需要调用该函数。

call MPI_SEND(buf, count, datatype, dest, tag, comm) int MPI_Send(type* buf, int count, MPI_Datatype, int dest, int tag, MPI_Comm comm)

该函数参数过多,不过这些参数都很有必要存在。

这些参数均为传入的参数,其中buf为你需要传递的数据的起始地址,比如你要传递一个数组A,长度是5,则buf为数组A的首地址。count即为长度,从首地址之后count个变量。datatype为变量类型,注意该位置的变量类型是MPI预定义的变量类型,比如需要传递的是C++的int型,则在此处需要传入的参数是MPI_INT,其余同理。dest为接收的进程号,即被传递信息进程的进程号。tag为信息标志,同为整型变量,发送和接收需要tag一致,这将可以区分同一目的地的不同消息。比如进程0给进程1分别发送了数据A和数据B,tag可分别定义成0和1,这样在进程1接收时同样设置tag0和1去接收,避免接收混乱。

6.MPI_RECV

该函数为MPI的接收函数,需要和MPI_SEND成对出现

call MPI_RECV(buf, count, datatype, source, tag, comm,status) int MPI_Recv(type* buf, int count, MPI_Datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

参数和MPI_SEND大体相同,不同的是source这一参数,这一参数标明从哪个进程接收消息。最后多一个用于返回状态信息的参数status。

在C和C++中,status的变量类型为MPI_Status,分别有三个域,可以通过status.MPI_SOURCE,status.MPI_TAG和status.MPI_ERROR的方式调用这三个信息。这三个信息分别返回的值是所收到数据发送源的进程号,该消息的tag值和接收操作的错误代码。

SEND和RECV需要成对出现,若两进程需要相互发送消息时,对调用的顺序也有要求,不然可能会出现死锁或内存溢出等比较严重的问题

4.5.3如何编写MPI代码

1:对于rank,size的理解

#include<bits/stdc++.h>
​
#include<mpi.h>
​
using namespace std;
​
int main(int argc,char * argv[])
​
{
​
  int myid,rank,size;
​
  MPI_Init(&argc,&argv); 
​
  MPI_Comm_rank(MPI_COMM_WORLD,&rank);
​
  MPI_Comm_size(MPI_COMM_WORLD,&size);
​
  cout<<"rank: "<<rank<<" size: "<<size<<endl; 
​
  MPI_Finalize(); 
​
  return 0;
​
}

编译:mpic++ first.cpp -o first

运行:mpirun -np 4 ./first

运行:mpirun -np 6 ./first     

 

                                           

 代码解读:首先需要添加头文件<mpi.h>。主函数内初始化,获得当前进程标识符和进程总数。但在代码内并没有进程数量大小的设置,而是在编译的时候指定进程数量。-np 后所跟数字即为进程数量。而rank size的值将非常重要的应用到后面的代码编写中。总的理解就是在MPI初始化后,所有的进程执行同一域内的代码,而每个进程的区分靠其独有的rank size的值。

2:对于发送、接收的理解

#include<bits/stdc++.h>
​
#include<mpi.h>
​
using namespace std;
​
int main(int argc, char *argv[])
​
{
​
  int rank,size,i,buf[1];
​
  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<100*(size-1);i++)
​
•    {
​
•      MPI_Recv(buf,1,MPI_INT,MPI_ANY_SOURCE,MPI_ANY_TAG,MPI_COMM_WORLD,&status);
​
•      cout<<"Msg= "<<buf[0]<<" from "<<status.MPI_SOURCE<<" with tag "<<status.MPI_TAG<<endl;
​
•    }
​
  }
​
  else
​
  {
​
•    for(i=0;i<100;i++)
​
•    {
​
•      buf[0]=rank+i;
​
•      MPI_Send(buf,1,MPI_INT,0,i,MPI_COMM_WORLD);
​
•    }
​
  }
​
  MPI_Finalize();
​
  return 0;
​
}

编译:mpic++ recive.cpp -o recive

运行:mpirun -np 4 ./recive

结果:

                                

代码分析:

MPI初始化后,通过循环下标控制多个进程向主进程发送消息,主进程接收并打印。发送和接收消息是对应的。

MPI矩阵乘

下方代码存在问题,double判断是否相等需要设置精度范围而不是直接相减为零,自己当时编码脑子不在线,大家注意别犯我的错误就好:)

代码:

#include <bits/stdc++.h>
​
#include <omp.h>
​
#include<mpi.h>
​
#include <immintrin.h>
​
using namespace std;
​
#define max 1024
​
#define location(i, j, n) (i * n + j)
​
typedef chrono::high_resolution_clock Clock;
​
const int n = max;
​
int *A = new int[max * max];
​
int *B = new int[max * max];
​
int *C = new int[max * max];
​
int *D = new int[max * max];
​
 
​
int main(int argc,char *argv[])
​
{
​
  cout << "Size of Matrix: " << n << endl;
​
  srand((int)time(0));
​
  for (int i = 0; i < n; i++)
​
  {
​
    for (int j = 0; j < n; j++)
​
    {
​
     A[location(i,j,n)]=1;
​
      B[location(i,j,n)]=1;
​
    }
​
  }
​
  auto startTime = Clock::now();
​
  int rank,size;
​
  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(int i=1;i<size;i++)
​
    {
​
      MPI_Send(&A[i*max/size*max],n*max/size,MPI_INT,i,9,MPI_COMM_WORLD);
​
      MPI_Send(B,max*max,MPI_INT,i,9,MPI_COMM_WORLD);
​
    }
​
    for(int i=0;i<max/size;i++)
​
    {
​
      for(int j=0;j<n;j++)
​
      {
​
        C[location(i,j,n)]=0;
​
        for(int k=0;k<n;k++)
​
        {
​
          C[location(i,j,n)]+=A[location(i,k,n)]*B[location(k,j,n)];
​
        }
​
      }
​
    }
​
  }
​
  else
​
  {  
​
      MPI_Recv (&A[rank*max/size*max],n*max/size,MPI_INT,0,9,MPI_COMM_WORLD,&status);
​
      MPI_Recv (B,max*max,MPI_INT,0,9,MPI_COMM_WORLD,&status);
​
      for(int i=rank*max/size;i<(rank+1)*max/size;i++)
​
      {
​
        for(int j=0;j<n;j++)
​
        {
​
          C[location(i,j,n)]=0;
​
          for(int k=0;k<n;k++)
​
          {
​
            C[location(i,j,n)]+=A[location(i,k,n)]*B[location(k,j,n)];
​
          }
​
        }
​
      }
​
        MPI_Send(&C[rank*(max/size)*max],max*max/size,MPI_INT,0,9,MPI_COMM_WORLD);
​
  }
​
  if(rank==0)
​
{
​
  for(int k=1;k<size;k++)
​
  {
​
      MPI_Recv(&C[k*(max/size)*max],max*max/size,MPI_INT,k,9,MPI_COMM_WORLD,&status);
​
  }
​
}
​
  auto endTime = Clock::now();
​
  auto compTime = chrono::duration_cast<chrono::microseconds>(endTime-startTime);
​
  cout<<"One core time :  "<<compTime.count()/1000<<"ms"<<endl;
​
MPI_Finalize();
​
  return 0;
​
}

编译指令:mpic++ mpi-matrix.cpp -o mpi-matrix

运行:mpirun -np 4 ./mpi-matrix

运行结果:

代码分析:

由于矩阵乘法的实现数据依赖性不强,通过简单的划分使得矩阵A的多行乘矩阵B。通过设置进程数使得每个进程获得A的一部分,进而独立运算后将数据回传,这样理论上将会带来速度的提升。但本实验测试环境为12代I5-12400F,进程切换以及资源的竞争导致运算速度并没有得到显著的提升。本例只是为演示MPI的基本操作。对于大规模集群计算,MPI跨界点可以带来计算速度以及计算能力的提升。

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值