基本函数

6个基本函数

MPI 有6个基本函数,从理论上讲 MPI 的所有通信功能都可以使用它的6个基本函数来实现。这 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 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 各参数的含义:

  • buf - 发送缓冲区的起始位置
  • count - 发送的数据个数
  • datatype - 发送数据的数据类型
  • dest - 目标进程标号
  • tag - 消息标志
  • comm - 通信域

MPI_Recv 各参数含义:

  • buf - 接受缓冲区地址
  • count - 最多可接受的数据个数
  • datatype - 接受的数据类型
  • source - 发送数据的进程号
  • tag - 消息标志
  • comm - 通信域
  • status - 返回状态

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

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

通过 MPI_Status,我们可以获得下面的三种主要信息

  • 发送进程的标号,存放在 MPI_SOURCE 属性中
  • 消息的标记号,存放在 MPI_TAG 属性中
  • 消息的长度,通过借助于 MPI_Get_count 函数,将 status 变量和数据类型传入,消息长度存放在 count
      MPI_Get_count(
          MPI_Status* status,
          MPI_Datatype datatype,
          int* count)
    

预定义数据类型

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

MPI预定义数据类型 对应的C数据类型
MPI_CHAR signed char
MPI_SHORT signed short int
MPI_INT signed int
MPI_LONG signed long int
MPI_LONG_LONG_INT long long int (optional)
MPI_UNSIGNED_CHAR unsigned char
MPI_UNSIGNED_SHORT unsigned short int
MPI_UNSIGNED unsigned int
MPI_UNSIGNED_LONG unsigned long int
MPI_FLOAT float
MPI_DOUBLE double
MPI_LONG_DOUBLR long double
MPI_BYTE
MPI_PACKED

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

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

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

任意源和任意标识

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

其他常用函数

这里记录一下 MPI 中其他的常用函数

获得当前时间

  • double MPI_Wtime(void)

获得机器名字

  • int MPI_Get_processor_name(char *name, int *result_len)
    • name - 当前进程所在机器的名字
    • result_len - 返回名字的长度

获得 MPI 版本

  • int MPI_Get_version(int *version, int *subversion)
    • version - MPI 主版本号
    • subversion - MPI 次版本号

判断 MPI_Init 是否执行,唯一一个可以在 MPI_Init 之前调用的函数。

  • int MPI_Initialized(int *flag) -
    • flag - 是否已调用

使通信域 Comm 中的所有进程退出

  • int MPI_Abort(MPI_Comm comm, int errorcode)
    • comm - 退出进程所在的通信域
    • errorcode - 错误码

示例

线程依次传参

#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();
}

运行结果

rank 0 send rank 1: 3
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 3 receive from 2: 6

使用 MPI_BYTE

下面是一个使用 MPI_BYTE 传递自定义结构体的例子。

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

typedef struct _custom_t {
    int a;
    double b;  
} custom_t;

int main() {
    int rank;
    MPI_Status status;
    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    custom_t msg;

    if(rank == 0) {
        msg.a = 2;
        msg.b = 10.0;
        MPI_Send(&msg, sizeof(custom_t), MPI_BYTE, 1, 99, MPI_COMM_WORLD);
    }

    if(rank == 1) {
        MPI_Recv(&msg, sizeof(custom_t), MPI_BYTE, 0, 99, MPI_COMM_WORLD, &status);
        printf("msg: a is %d and b is %.2f\n", msg.a, msg.b);
    }

    MPI_Finalize();
}

获得接受信息的长度

在接收完消息后,获得消息的长度。要求接受缓冲区的长度要大于消息的长度

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

int main() {
    int n = 10;
    int half_n = 5;
    int buffer[n];
    int i;
    int rank;
    MPI_Status status;

    for(i = 0; i < half_n; i++) {
        buffer[i] = i;
    }

    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if(rank == 0) {
        MPI_Send(&buffer,half_n, MPI_INT, 1, 99, MPI_COMM_WORLD);
    }

    if(rank == 1) {
        int receive_count = 0;
        MPI_Recv(&buffer, n, MPI_INT, 0, 99, MPI_COMM_WORLD, &status);
        MPI_Get_count(&status, MPI_INT, &receive_count);
        printf("rank 1 receive %d int\n", receive_count);

        for(i = 0; i < n; i++) {
            printf("buffer[%d] is %d\n", i, buffer[i]);
        }
    }

    MPI_Finalize();
}

上面的方法不太灵活,因为在接受消息的时候我们仍然不知道消息的长度,在 Recv函数中的 count 不太好指定。通过使用 MPI_Probe 方法,我们可以事先获取要接受的消息的相关信息,可以灵活的接受消息。 下面是 MPI_Probe 函数的原型:

MPI_Probe(
    int source,
    int tag,
    MPI_Comm comm,
    MPI_Status* status)

通过传入发送进程的进程号,消息标记以及通信域,就可以提前获得消息的 status,下面是一个示例:

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

int main() {
    int n = 10;
    int half_n = 5;
    int buffer[n];
    int i;
    int rank;
    MPI_Status status;

    for(i = 0; i < half_n; i++) {
        buffer[i] = i;
    }

    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if(rank == 0) {
        MPI_Send(&buffer,half_n, MPI_INT, 1, 99, MPI_COMM_WORLD);
    }

    if(rank == 1) {
        int receive_count = 0;

        MPI_Probe(0, 99, MPI_COMM_WORLD, &status);
        // 在实际接受消息之前获得消息的长度    
        MPI_Get_count(&status, MPI_INT, &receive_count);

        MPI_Recv(&buffer, receive_count, MPI_INT, 0, 99, MPI_COMM_WORLD, &status);
        printf("rank 1 receive %d int\n", receive_count);

        for(i = 0; i < n; i++) {
            printf("buffer[%d] is %d\n", i, buffer[i]);
        }
    }    
    MPI_Finalize();
}

使用任意源和任意标识

线程 1 到 n-1 向线程 0 发送消息

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

int main() {
    int rank, size;
    int value = 0;
    int i;
    MPI_Status status;
    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    if(rank == 0) {
        for(i = 0; i < size -1; i++) {
            MPI_Recv(&value, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
            printf("receive from process %d and values is %d\n", status.MPI_SOURCE, value);
        }
    } else {
        value = rank * 2;
        MPI_Send(&value, 1, MPI_INT, 0, rank, MPI_COMM_WORLD);
    }

    MPI_Finalize();
}

示例输出

receive from process 2 and values is 4
receive from process 3 and values is 6
receive from process 1 and values is 2

results matching ""

    No results matching ""

    results matching ""

      No results matching ""