MPI并行计算学习

2020/02/21

Categories: 笔记 Tags: C 并行计算

并行计算(Parallel Computing)是指同时使用多种计算资源解决计算问题的过程,是提高计算机系统计算速度和处理能力的一种有效手段。它的基本思想是用多个处理器来协同求解同一问题,即将被求解的问题分解成若干个部分,各部分均由一个独立的处理机来并行计算。

在win+vs上运行MPI指令

配置就不记录了,网上有很多。

win+vs+MPI,不能像常规程序一样直接运行。

  1. 先在vs里写好代码
  2. 重新生成项目
  3. 找到项目名.exe所在文件夹
  4. Shift+鼠标右键,打开Powershell窗口
  5. 输入命令(比如): mpiexec -n 10 HelloWord_MPI.exe

注意:

mpiexec是运行MPI并行程序的指令,基本格式为
mpiexec -n N ./xxxxxx
其中N是并行进程数,xxxxxx是并行程序名称。

MPI

MPI是一个跨语言的通讯协议,用于编写并行计算机。

6个基本函数

理论上讲 MPI 的所有通信功能都可以使用它的6个基本函数来实现。

//初始化
MPI_Init(int *argc,char ***argv)

//MPI 结束调用
MPI_Finalize(void)

/*
*获得进程的标识号
*保存在rank里
*/ 
MPI_Comm_rank(MPI_Comm comm,int *rank)    

/*
*获得当前通信域中进程的个数
*保存在size里
*/
MPI_Comm_size(MPI_Comm comm,int *size)    

//发送消息
MPI_Send(Void *buf,int count,MPI_Datatype,int dest,int tag,MPI_Comm comm)    

//接受消息    
MPI_Recv(void *buf,int count,MPI_Datatype data,int source,int tag,MPI_Comm comm,MPI_Status *status)   

消息传递

MPI 通过 MPI_SendMPI_Receive 发送和接受消息。其中这两种属于阻塞通信,对于发送方来说,消息发送不出去就会一直等待,而对于接收方来说,接收不到消息就会一直等待。

MPI_Send 各参数的含义:

MPI_Recv 各参数含义:

发送和接受的时候是以指定的 datatype 为基本单位的,count 是 datatype 类型数据的数目。在接受数据时,接受缓冲区的长度可以大于发送数据的长度。但是 MPI 中没有数据截断,如果发送数据长度大于接受缓冲区的长度就会报错。

在 C 实现中,状态变量 MPI_Status 必须要包含 3 个信息:MPI_SOURCE, MPI_TAGMPI_ERROR,除此之外还可以包含其它的附加域。

详细说明:

MPI_Send:

MPI_Recv:

预定义数据类型

MPI 预定义了下面几种数据类型

MPI预定义数据类型

对应的C数据类型

在传递信息的时候,要保证两个方面的类型匹配(除了 MPI_BYTEMPI_PACKED ):

  1. 传输的数据类型和通信中声明的 MPI 类型要对应,即数据类型为 int, 那么通信时声明的数据类型就要为 MPI_INT
  2. 发送方和接受方的类型要匹配

MPI_BYTEMPI_PACKED 可以和任意以字节为单位的存储相匹配。 MPI_BYTE 可以用于不加修改的传送内存中的二进制值。

任意源和任意标识

在消息传递时,发送操作必须明确指定发送对象的进程标号和消息标识,但是接收消息时,可以通过使用 MPI_ANY_SOURCEMPI_ANY_TAG 来接受任意进程发送给本进程的消息,类似于通配符。MPI_ANY_SOURCEMPI_ANY_TAG 可以同时使用或者分别单独使用。

Hello World

#include <stdio.h>
#include "mpi.h"

int main(int argc,char *argv[]){
    int rank;
    int size;
    MPI_Init( &argc, &argv );
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    printf( "Hello world from process %d of %d\n", rank, size );
    MPI_Finalize();
    return 0;
}
Hello world from process 0 of 1

举例

1-n号进程发送,0号进程接收

此为标准阻塞接收发送的方式

#include <stdio.h>
#include <string.h>
#include "mpi.h"
void main(int argc, char* argv[])
{
    int numprocs, myid, source;
    MPI_Status status;
    char message[100];
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &myid);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    if (myid != 0) {  //非0号进程发送消息
        strcpy(message, "Hello World!");
        MPI_Send(message, strlen(message) + 1, MPI_CHAR, 0, 99,
            MPI_COMM_WORLD);
    }
    else {   // myid == 0,即0号进程接收消息
        for (source = 1; source < numprocs; source++) {
            MPI_Recv(message, 100, MPI_CHAR, source, 99,
                MPI_COMM_WORLD, &status);
            printf("接收到第%d号进程发送的消息:%s\n", source, message);
        }
    }
    MPI_Finalize();
} /* end main */
PS F:\并行程序学习\HelloWord_MPI\X64\Debug> mpiexec -n 4 HelloWord_MPI.exe
接收到第1号进程发送的消息:Hello World!
接收到第2号进程发送的消息:Hello World!
接收到第3号进程发送的消息:Hello World!
PS F:\并行程序学习\HelloWord_MPI\X64\Debug> mpiexec -n 8 HelloWord_MPI.exe
接收到第1号进程发送的消息:Hello World!
接收到第2号进程发送的消息:Hello World!
接收到第3号进程发送的消息:Hello World!
接收到第4号进程发送的消息:Hello World!
接收到第5号进程发送的消息:Hello World!
接收到第6号进程发送的消息:Hello World!
接收到第7号进程发送的消息:Hello World!

可以看到,当开启四线程运行时,1-3号进程发送消息,0号进程接收到消息并打印;当开启八线程运行时,1-7号进程发送消息,0号进程接收到消息并打印。

线程依次传参

#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"

/**
 * 数据传递
 *
 * 线程 i 向线程 i+1 传数据
 */
int main() {
    int param;
    int process_num;
    int process_id;
    MPI_Status status;
    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &process_id);
    MPI_Comm_size(MPI_COMM_WORLD, &process_num);

    if(process_id == 0) {
        param = 3;
        printf("rank 0 send rank 1: %d\n", param);
        param++;
        MPI_Send(&param, 1, MPI_INT, 1, 99, MPI_COMM_WORLD);
    } else {
        MPI_Recv(&param, 1, MPI_INT, process_id - 1, 99, MPI_COMM_WORLD, &status);
        printf("rank %d receive from %d: %d\n",process_id, process_id-1, param);
        if(process_id < process_num -1) {
            param++;
            printf("rank %d send rank %d: %d\n", process_id, process_id+1, param);
            MPI_Send(&param, 1, MPI_INT, process_id + 1, 99, MPI_COMM_WORLD);
        }
    }
    MPI_Finalize();
}
PS F:\并行程序学习\HelloWord_MPI\X64\Debug> mpiexec -n 4 HelloWord_MPI.exe
rank 1 receive from 0: 4
rank 1 send rank 2: 5
rank 2 receive from 1: 5
rank 2 send rank 3: 6
rank 0 send rank 1: 3
rank 3 receive from 2: 6

通信类别

对于 MPI 中的通信中,从通信对象方面看可以分为两类:

对于点对点通信来讲,从发送行为不同又可以分为下面三类:

对于组通信来说,会为组内的进程隐式添加一个同步点,等到所有进程到达后再往下执行。在后面的文章我们会详细介绍上面提到的通信类别。

>> Home