简介

MPI是一种编程规范,它的实现是一个库,主要在Fortran和c语言上实现。它的目的是实现进程间通讯。

MPI基本函数

开始与结束

  • MPI_Init(&argc, &argv): 利用输入进来的参数进行初始化
  • MPI_Finalize(): MPI终止时调用
#include <stdio.h>
#include <mpi.h>
void main (int argc, char * argv[])
{
int err;err = MPI_Init(&argc, &argv);
printf("Hello world!\n");
err = MPI_Finalize();
}

运行

mpirun -n 4 ./hello.c

身份标识

  • MPI_COMM_WORLD: 获得通信域(也就是同一组内的进程,例如上面mpirun -4代表启动4个进程,他们属于同一组)
  • MPI_Comm_size(MPI_COMM_WORLD, &size): 获得进程个数,赋值给size
  • MPI_Comm_rank(MPI_COMM_WORLD, &rank): 获得此进程在通信域内的编号,赋值给rank

数据类型

基本数据类型有:

自定义结构体:

typedef struct
{
int a[4];
double b[4];
}node;

types[0] = MPI_INT;
length[0] = 4;
displacements[0] = 0;
types[1] = MPI_DOUBLE;
length[1] = 4;
displacements[1] = 4;

MPI_Type_struct(2, lengths, displacements, types, &buff_datatype);
MPI_Type_commit(&buff_datatype);
然后就可以使用buff_dtattype这种类型了,这种类型也就表示了node

信息传递

  • int MPI_Send( void *buff, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)

    • buff: 是存储消息缓存的起始地址(一般用数组)
    • count: 传递定义数据类型的多少项
    • datatype: 传递的数据类型
    • dest: 是目的进程的编号(由对方的MPI_Comm_rank获得的)
    • tag: 消息标签,接收方的消息标签要和发送方的消息标签相同才可以接受,一两位目的进程可能不知接收一条信息,它需要标签进行区分。
    • 标识通信域,一般使用MPI_Comm_WORLD
  • int MPI_Recv( void *buff, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status):

    • buff: 用来接收消息的缓冲区
    • 接收的最大项数
    • source: 消息来源进程编号
    • tag: 双方约定的信号量(这个源和目的地都应该相同,不然收不到)
    • status: 状态信息,包括消息的源进程标识,消息标签,包含的数据项个数等。Status.MPI_SOURCE, Status.MPI_TAG , MPI_Get_count(&Status, MPI_INT, &C)可以读出
#include "mpi.h"
int foo(int i){...}
main(int argc,char* argv[])
{
int i, tmp, sum=0, group_size, my_rank, N;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &group_size);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
if (my_rank==0)
{
printf("Enter N:");
scanf("%d",&N);
for (i=1;i<group_size;i++)
MPI_Send(&N, 1, MPI_INT, i, i, MPI_COMM_WORLD);
for (i=my_rank;i<N;i=i+group_size)
sum=sum+tmp;
for (i=1;i<group_size;i++)
{
MPI_Recv(&tmp,1,MPI_INT,i,i,MPI_COMM_WORLD,&status);
sum=sum+tmp;
}
printf("\n The result = %d", sum);
}
else
{
MPI_Recv(&N,1,MPI_INT,0,my_rank,MPI_COMM_WORLD,&status);
for (i=my_rank;i<N;i=i+group_size)
sum=sum+foo(i);
MPI_Send(&sum,1,MPI_INT,0,my_rank,MPI_COMM_WORLD);
}
MPI_Finalize();
}
  • int MPI_Sendrecv(void *sendbuf, int sendcount,MPI_Datatype sendtype, int ,dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, int recvtag, MPI_Comm comm, MPI_Status *status): 它的作用是捆绑发送接收,他可以避免由于发送接收次序错误而造成的死锁。同时它可以接收单个MPI_Send发来的消息也可以呗:MPI_Recv接收到

  • MPI_PROC_NULL: 虚拟进程,当一个进程向这个进程发送消息时,会立刻成功返回,接收消息时也会立刻成功返回并且缓冲区没有任何改变。它可以写在source或dest中

组通信

  • MPI_Bcast( void* data, int count, MPI_Datatype datatype, int root,MPI_Comm communicator): 标号为root的进程给其他所有进程发送消息,包括他自己,对于root来说,data即是发送缓冲又是接收缓冲
    MPI_Init( &argc, &argv );
    MPI_Comm_rank( MPI_COMM_WORLD, &rank );
    do
    {
    if (rank == 0)
    scanf( "%d", &value );
    MPI_Bcast( &value, 1, MPI_INT, 0, MPI_COMM_WORLD );//其他进程也要写,用来接收
    printf( "Process %d got %d\n", rank, value );
    } while (value >= 0);
    MPI_Finalize( );
  • MPI_Scatter(
    void send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void
    recv_data,
    int recv_count,
    MPI_Datatype recv_datatype,
    int root,
    MPI_Comm communicator)
    • root发送给每个分组缓冲区中的一部分信息,非root进程会忽略发送缓冲。它是将每个接收缓冲区填满之后才会发给下一个进程,而不是平均分。

  • MPI_Gather(
    void send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void
    recv_data,
    int recv_count,
    MPI_Datatype recv_datatype,
    int root,
    MPI_Comm communicator)
    • 只有root进程的recv_buf是有效的,其他可以随便传
    • 它是接受所有进程的信息汇总到root进程中(包括它自己的信息).
    • 接收信息是按照序号进行排列的,并且recv_count只需要填写一个进程接收的信息就可以了

例如:

int *recv_buf;
if(rank == 0)
{
recv_buf = (int*)malloc(sizeof(int) * all * all);
}
int *send_buf = (int*)malloc(sizeof(int) * add_sum * add_sum);
int beginx = rank / sqrt_num * add_sum;
int beginy = rank % sqrt_num * add_sum;
int flag = 0;
for(int i=beginx; i<beginx+add_sum; i++)
{
for(int k=beginy; k<beigny+add_sum; k++)
{
send_buf[flag++] = i * add_sum + k;
}
}
MPI_Gather(send_buf, add_sum * add_sum, MPI_INT, recv_buf, add_sum * add_sum, MPI_INT, 0, MPI_COMM_WORLD);

  • MPI_Allgather(
    void send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void
    recv_data,
    int recv_count,
    // v: int recvcnt, int disp
    MPI_Datatype recv_datatype,
    MPI_Comm communicator):
    • 每个进程都把自己的信息发给其他所有进程,完成之后所有进程都有信息的聚合

  • MPI_Reduce(inbuf,result,count,datatype,op,root,comm): 进行聚合之后还进行了一次归约操作,归约操作有MAX, MIN, SUM, PROD, LAND, BAND(按位与), LOR, BOR, LXOR, BXOR, MAXLOC, MINLOC等。这是每一个数据每一个数据进行归约
  • MPI_AllReduce: 和MPI_Reduce类似,没有root参数
  • MPI_Reduce_Scattar(void send_data, void recv_data, int recvcounts, Op, Comm): 对数据进行归约之后再分给每个进程。
  • MPI_Scan(void sendbuf, void recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm): 每个进程都对它前面的进程进行归约,类似前缀和

  • int MPI_Alltoall(void sendbuf, int sendcount, MPI_Datatype senddatatype, void recvbuf, int recvcount, MPI_Datatype recvdatatype, MPI_Comm comm): 每个进程把第i块数据发送给第i个进程,又从第j个进程接收数据到自己的第j块中。

例如: 进程1把自己第二块数据发送给进程2, 进程2接收到来自进程1的数据放到 第一块中,类似于矩阵转置。

  • MPI_Comm(Comm): 等待同一群组中所有进程到达屏障时候才进行下一步操作

多线程MPI

  • MPI_INIT_THREAD(int *argc, char ***argv, int required, int *provided)
    • required: 是线程的需求级别,它决定了那些线程可以进行MPI通信
      • MPI_THREAD_SINGLE: 单线程
      • MPI_THREAD_FUNNELED: 只有主线程可以进行MPI通信
      • MPI_THREAD_SERIALIZED: 同一时刻只有一个线程可以进行MPI通信
      • MPI_THREAD_MULTIPLE: 任何线程都可以随意进行通信
    • provided: 可以提供的线程
    • 设定了级别之后就可以在MPI内部用多线程了