MPI并行编程——多进程程序设计

Posted 时栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MPI并行编程——多进程程序设计相关的知识,希望对你有一定的参考价值。

MPI(Massage Passing Interface),它不是一种语言,而是一种库描述,是消息传递函数库的标准规范。MPI标准定义了一组具有可移植性的编程接口,在Fortran和C/C++中可以直接对相应的函数进行调用。

MPI有很多种实现。MPICH是最重要的MPI实现之一,它与其衍生产品构成了世界上使用最广泛的MPI实现,在超级计算机中也得到了广泛的应用。

MPI最基本的消息传递操作包括:发送消息send、接受消息receive、进程同步barrier、归约reduction等。本文对发送消息接受消息作简要的介绍,如要了解更多MPI的相关知识,请参考其他相关资料。

一、MPI基本概念与相关的函数解释

通信器(communicator)

所谓通信器,可以理解为一类进程的集合,也可以称之为一个进程组。一个进程组中的进程可以相互通信。所有MPI通信都必须在某个通信器内进行。MPI系统提供缺省的通信器MPI_COMM_WORLD,所有启动的MPI进程通过调用函数MPI_Init()包含在该通信器内。各个进程可以通过函数MPI_Comm_size()获取通信器所包含的的MPI进程个数。一个通信器内的所有进程都拥有一个该通信器内中唯一的序号(rank)用作自身的标识( 序号的取值范围是[0,通信器进程数-1]

int MPI_Comm_size(MPI_Comm comm, int* size)
//获取通信器的进程数,comm为通信器名称(也叫通信域),获取得到的进程个数结果被放在size中

进程序号(rank)

rank用来在一个通信器中标识一个进程,同一个进程在不同的通信器中可以有不同的序号,进程的序号是在通信器被创建时赋予的。

int MPI_Comm_rank(MPI_Comm comm, int* rank)
//获取进程在通信器中的标号,comm为通信器名称,获取得到的进程id被放在rank中

消息(message)

不同进程之间通过消息传递数据,因此MPI可以应用于分布存储系统。消息发送与接收函数的参数可以分为数据(data)和信封(envelope)两个部分。包装由进程序号、消息标号和通信器三部分组成;

int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
//消息发送函数,参数可以分为数据、信封
//数据:<地址(buffer的地址),数据个数(count),数据类型(datatype)>
//信封:<目的(dest),标识(tag),通信域(comm)>
//count指定数据类型的个数、datatype为数据类型、dest取值范围是0~(进程总数-1);tag 取值范围是0~MPI_TAG_UB,用来区分消息
    
int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source,int tag, MPI_Comm comm, MPI_Status *status)
//接收buffer必须至少可以容纳count个由datatype参数指明类型的数据. 如果接收buf太小, 将导致溢出。
//消息匹配机制:
//1.参数匹配 dest,tag,comm 分别与 source,tag,comm相匹配;
//2.若 Source 为 MPI_ANY_SOURCE:接收任意处理器来的数据。
//3.若Tag为MPI_ANY_TAG:则匹配任意tag值的消息
//在阻塞式消息传送中不允许Source==Dest,否则会导致死锁。
//消息传送被限制在同一个通信域中。
//在send函数中必须指定唯一的接收者

int MPI_Get_count(MPI_Status status, MPI_Datatype datatype, int*count)
//查询接收到的消息长度,该函数在count中返回数据类型的个数,即消息的长度(count属于MPI_Status结构的一个域,但不能被用户直接访问)

需要注意:

  1. 发送进程需指定一个有效的目标接收进程

  1. 接收进程需指定一个有效的源发送进程

  1. 接收和发送消息的进程要在同一个通信器内

  1. 接收和发送消息的tag要相同

  1. 接收缓存区要足够大

二、MPI环境配置

2.1 gcc/g++环境配置

Ubuntu默认没有安装gcc/g++等编译环境。为简化环境配置,可以使用build-essential软件包。该软件包内包含了gcc/g++/gfortran等编译器.

sudo apt install build-essential

2.2 mpich环境配置

可直接使用如下命令安装mpich。

sudo apt install mpich

2.3 版本信息查看

通过在命令行输入以下信息查看程序版本

gcc版本查看(C语言编译器)

gcc -v

g++版本查看(C++编译器)

g++ -v

mpicc版本查看(使用C语言编写的MPI程序,需要使用mpicc进行编译)

mpicc -v

mpic++版本查看(使用C++编写的MPI程序,需要使用mpic++进行编译)

mpic++ -v

三、MIP的编译与运行

3.1程序的编译

使用以下命令可以编译mpi程序:

使用C语言编写的MPI程序:
     mpicc -o XXX XXX.cpp

使用C++编写的MPI程序:
     mpic++ -o XXX XXX.cpp

3.2 程序的运行

mpi程序的运行:

mpirun -np Y ./XXX
//  Y表示的是一个数字,代表并行运行的进程数目。
//  XXX表示源程序文件

3.3 HelloWorld示例程序

#include <iostream>
#include "mpi.h"
//使用了MPI程序需要包含mpi.h头文件
using namespace std;

int main(int argc, char** argv)

    MPI_Init( &argc , &argv);
    cout<<"Hello world!"<<endl;
    MPI_Finalize();
    return 0;

该程序只是使每个进程输出hello world!运行结果如下:

四、MPI数据类型(与C语言对应)

MPI 数据类型

C语言数据类型

MPI_CHAR

char

MPI_SHORT

short int

MPI_INT

int

MPI_LONG

long

MPI_FLOAT

float

MPI_DOUBLE

double

MPI_LONG_DOUBLE

long double

MPI_BYTE

MPI_PACKED

五、MPI基本函数

int MPI_Init(int *argc, char **argv)
//初始化函数。MPI_INIT是MPI程序的第一个调用,完成MPI程序的所有初始化工作,启动MPI环境,标志并行代码的开始,因此要求main函数必须带参数运行

int MPI_Finalize(void)
//退出/结束函数。它是MPI程序的最后一个调用,结束MPI程序的运行,是MPI程序的最后一条可执行语句。它标志并行代码的结束,结束除主进程外其它进程
    
int MPI_Initialized(int* flag)
//允许在MPI_Init前使用的函数,检测MPI系统是否已经初始化


int MPI_Get_processor_name(char* name, int* resultlen)
//获取处理器的名称,在返回的name中存储所在处理器的名称,resultlen存放返回名字所占字节,应提供参数name不少于MPI_MAX_PRCESSOR_NAME个字节的存储空间

double MPI_Wtime(void)
//返回调用时刻的墙上时间,用浮点数表示秒数,墙上时钟时间wall clock time,也叫时钟时间,是指从进程从开始运行到结束,时钟走过的时间,这其中包含了进程在阻塞和等待状态的时间。用来计算程序运行时间。

六、几个简单示例程序

6.1 Print

输出每个进程的信息和运行时间

#include <iostream>
#include "mpi.h"
using namespace std;

int main(int argc, char** argv)

    int pid, pnum,namelen;
    double starttime;
    char processor_name[MPI_MAX_PROCESSOR_NAME];

    MPI_Init( &argc , &argv);
    //初始化
    starttime=MPI_Wtime();
    //开始时间
    MPI_Comm_size( MPI_COMM_WORLD , &pnum);
    //获取通信域内进程个数
    MPI_Comm_rank( MPI_COMM_WORLD , &pid);
    //获取本进程的rank
    MPI_Get_processor_name( processor_name , &namelen);
    //获取processor_name
    cout<<"this is "<<pid<<" of "<<pnum<<" and my name is "<< processor_name <<endl;
    cout<<"this is "<<pid<<" and spend ";
    printf("%.6lf s\\n",MPI_Wtime()-starttime);
    //输出信息
    MPI_Finalize();
    //结束并退出

    return 0;

命令行:

mpic++ -o print.o print.cpp

mpirun -np 4 ./print.o

6.2 π的计算

#include <iostream>
#include "mpi.h"
using namespace std;



int main(int argc, char**argv)

    //mpirun -np 4 calculatePI.o 800   其中的800是以参数的形式传入的,位于argv[1]
    long double pi=0, answer=0, PI=3.141592653589793238462643383279;
    int size, id, namelen,n=1000;
    double time;
    char processor_name[MPI_MAX_PROCESSOR_NAME];
    MPI_Status status;
    //开始计时
    time=MPI_Wtime();

    //如果参数列表中制定了n的值,则将该值赋给n
    if(argc==2)sscanf(argv[1], "%d", &n);
    MPI_Init(&argc, &argv);
    //获取进程信息
    MPI_Comm_size( MPI_COMM_WORLD , &size);
    MPI_Comm_rank( MPI_COMM_WORLD , &id);
    
    //比较n和size大小,若n过小,则返回
    if(n<size)
    
        cout<<"输入n值过小, 请重新输入"<<endl;
        MPI_Finalize();
        return 0;
    

    for(int i=id; i<=n; i+=size)
    
        long double tempans;
        tempans = (long double)1/(long double)(2*i+1);
        if(i%2==0)
        
            answer+=tempans;
        
        else
        
            answer-=tempans;
        
    
    
    //如果是主进程
    if(id==0)
    
        long double recvbuf;
        pi=answer;
        for(int i=1; i<size; i++)
        
            MPI_Recv( &recvbuf , 1 , MPI_LONG_DOUBLE , MPI_ANY_SOURCE , 0 , MPI_COMM_WORLD , &status);
            pi+=recvbuf;
        
    
    else//发送消息并退出程序
    
        MPI_Send( &answer , 1 , MPI_LONG_DOUBLE , 0 , 0 , MPI_COMM_WORLD);
        MPI_Finalize();
        return 0;
    
    


    pi*=4;
    cout<<"主进程使用"<<MPI_Wtime()-time<<"秒, 最后得到PI的计算结果为: ";
    printf("%.20Lf\\n",pi); 
    cout<<"n = "<<n<<" 使用了 "<<MPI_Wtime()-time <<"s pi = ";
    printf("%.20Lf  %.20Lf \\n",pi,abs(PI-pi)); 
    MPI_Finalize();
    return 0;

命令行:

mpic++ -o calculatePI.o calculatePI.cpp

mpirun -np 1 calculatePI.o 200000000

mpirun -np 4 calculatePI.o 200000000

运行结果为:

如有不当或错误之处,恳请您的指正,谢谢!!!

以上是关于MPI并行编程——多进程程序设计的主要内容,如果未能解决你的问题,请参考以下文章

多进程程序设计,王明学learn

Java多线程程序设计初步入门

写出java多线程程序设计中常用类及方法名,并分别说明它们的作用。

DELPHI下的多线程程序设计

C++11 并发编程基础:并发并行与C++多线程

POSIX多线程程序设计_流水线工作例程