MPI代表Message Passing Interface(消息传递接口),是一种用于在并行计算中进行通信的标准。MPI是一组库和规范,允许多个处理单元(通常是计算集群或超级计算机中的节点)在并行执行的程序之间进行通信和数据传输。
MPI的主要目标是提供一个标准的、可移植的编程接口,使得开发者能够方便地编写并行程序,而不用担心底层硬件和通信机制的细节。MPI支持点对点通信和集体通信,使得并行程序可以在不同的处理单元之间交换信息,协同完成任务。
总结一下,mpi的任务:
MPI_Init 和 MPI_Finalize
初始化MPI 环境和结束MPI 环境,也就是MPI代码需要在这2个期间使用,实例代码如下:
//001.c
#include <stdio.h>
#include <mpi.h>
int main(int argc, char **argv)
{
int numprocs;
MPI_Init(&argc, &argv);
//your code here
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
//end of your code
printf("Hello World! The number of processes is %d\n",numprocs);
MPI_Finalize();
return 0;
}
mpicc -o a 001.c #编译
mpicc -np 4 ./a 开启4核,并且运行
结果如下:
MPI_Comm_rank 和 MPI_Comm_size
MPI_Comm_rank :用于获取调用进程在指定通信组(MPI_Comm
)中的秩(rank)。秩是一个在通信组内唯一标识进程的整数值。每个进程都可以调用 MPI_Comm_rank
函数来获得自己在通信组中的秩。
函数原型如下:
int MPI_Comm_rank(MPI_Comm comm, int *rank);
其中,comm 表示 一个通信组,一般用MPI_COMM_WORLD ,它表示所有MPI进程的通信组。
rank :是一个指向整数的指针,用于存储调用进程在通信组中的秩。
MPI_Comm_size : 是 MPI(Message Passing Interface)库中的一个函数,用于获取指定通信组(MPI_Comm
)中的进程总数。这个函数返回通信组中的进程数量。
函数原型如下:
int MPI_Comm_size(MPI_Comm comm, int *size);
示例如下:
//002.c
#include <stdio.h>
#include <mpi.h>
int main(int argc, char **argv)
{
int myid, numprocs;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
//your code here
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
//end of your code
printf("Hello World!I'm rank %d of %d\n", myid, numprocs);
MPI_Finalize();
return 0;
}
结果如下:
MPI_SendMPI 和 MPI_Recv
MPI_Send : 点对点,从 MPI 中一个进程,发送到另一个进程中。原型如下:
int MPI_Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);
buf
:发送数据的缓冲区的起始地址。count
:发送的数据项数量。datatype
:发送数据项的数据类型。dest
:目标进程的秩(rank)。tag
:消息标签,用于标识消息的类型。comm
:通信组,通常使用MPI_COMM_WORLD
,表示所有 MPI 进程的默认通信组。
MPI_Recv:对应上面的,接收函数,原型如下:
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status);
buf
:接收数据的缓冲区的起始地址。count
:接收的数据项数量。datatype
:接收数据项的数据类型。source
:发送进程的秩(rank),指定从哪个进程接收消息。如果指定为MPI_ANY_SOURCE
,则接收来自任何进程的消息。tag
:消息标签,用于标识消息的类型。如果指定为MPI_ANY_TAG
,则接收任何标签的消息。comm
:通信组,通常使用MPI_COMM_WORLD
,表示所有 MPI 进程的默认通信组。status
:一个包含接收操作的状态信息的结构体,可以设置为MPI_STATUS_IGNORE
,表示不关心状态信息。
以下是一个简单的例子:
//003.c
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int my_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
if (my_rank == 0) {
int data = 42;
MPI_Send(&data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (my_rank == 1) {
int received_data;
MPI_Recv(&received_data, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process 1 received data: %d\n", received_data);
}
MPI_Finalize();
return 0;
}
在003.c 代码中,rank(0) 会将43发送到rank(1)中的received_data 中
结果如下:
MPI_Bcast 和 MPI_Scatter
MPI_Bcast:是一种集体通信操作,用于向通信组中的所有进程广播数据,也就是将一个进程的数据传递给所有其他进程,使得每个进程都能获得相同的数据。
函数原型如下:
int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm);
buffer
:要广播的数据的缓冲区的起始地址。count
:广播的数据项数量。datatype
:广播的数据项的数据类型。root
:广播操作的根进程的秩(rank),该进程的数据将被广播到其他所有进程。comm
:通信组,通常使用MPI_COMM_WORLD
,表示所有 MPI 进程的默认通信组。
以下是一个简单的例子:
//004.c
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int my_rank, data;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
if (my_rank == 0) {
data = 42;
}
// 使用 MPI_Bcast 将进程0中的 data 广播到所有进程
MPI_Bcast(&data, 1, MPI_INT, 0, MPI_COMM_WORLD);
printf("Process %d has data: %d\n", my_rank, data);
MPI_Finalize();
return 0;
}
结果如下:
但并不需要显式调用 MPI_Bcast 函数来接收数据,因为这个函数会在所有进程中同步执行。
在 MPI_Bcast 中,广播源(根进程)的数据会被复制到所有其他进程的指定缓冲区,因此所有进程都有相同的数据。
注意:在这个例子中,每个进程在调用 MPI_Bcast
后都可以直接使用 data
变量,因为 MPI_Bcast
会将根进程的数据复制到每个进程的 data
变量中。在这里,每个进程的 data
变量都将包含值为 42 的数据。
所以,接收进程不需要额外的步骤来获取数据,它们可以直接使用 MPI_Bcast
同步到的数据。
MPI_Gather
MPI_Gather:一种集体通信操作,用于收集通信组中所有进程的数据到一个进程中。通常,这些数据是在每个进程中生成或计算的,然后通过 MPI_Gather
被收集到根进程中。
int MPI_Gather(const void *sendbuf, int sendcount, MPI_Datatype sendtype,
void *recvbuf, int recvcount, MPI_Datatype recvtype,
int root, MPI_Comm comm);
sendbuf
:要发送的数据的缓冲区的起始地址。sendcount
:每个发送进程中发送块中的数据项数量。sendtype
:发送进程中发送数据项的数据类型。recvbuf
:接收数据的缓冲区的起始地址(只在根进程中有效)。recvcount
:根进程中接收块中的数据项数量。recvtype
:根进程中接收的数据项的数据类型。root
:进行收集的根进程的秩(rank)。comm
:通信组,通常使用MPI_COMM_WORLD
,表示所有 MPI 进程的默认通信组。
以下是一个简单的例子:
//005.c
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int my_rank, data;
int gathered_data[4]; // 4进程,每个进程有一个数据项
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
data = my_rank + 1;
// 使用 MPI_Gather 将每个进程中的数据收集到根进程中
MPI_Gather(&data, 1, MPI_INT, gathered_data, 1, MPI_INT, 0, MPI_COMM_WORLD);
if (my_rank == 0) {
printf("Root process received data: ");
for (int i = 0; i < 4; ++i) {
printf("%d ", gathered_data[i]);
}
printf("\n");
}
MPI_Finalize();
return 0;
}
结果…很奇怪,似乎 MPI_Gather 有提前同步的功能…
MPI_Reduce
MPI_Reduce用于在通信组中的所有进程之间进行全局归约(reduction)操作。归约操作是指将多个进程中的局部数据合并成一个全局结果。
函数原型如下:
int MPI_Reduce(const void *sendbuf, void *recvbuf, int count,
MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);
sendbuf
:要归约的局部数据的缓冲区的起始地址,每个进程的数据。recvbuf
:接收归约结果的缓冲区的起始地址,只在根进程中有效。count
:每个进程中的数据项数量。datatype
:数据项的数据类型。op
:归约操作,例如MPI_SUM
表示求和,MPI_MAX
表示求最大值,等等。可以使用其他的 MPI_Op 常量,也可以自定义归约操作。root
:进行归约操作的根进程的秩(rank)。comm
:通信组,通常使用MPI_COMM_WORLD
,表示所有 MPI 进程的默认通信组。
以下是一个简单的例子,演示如何使用 MPI_Reduce
进行全局求和:
//006.c
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int my_rank, data;
int global_sum;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// 假设每个进程有一个整数数据项
data = my_rank + 1;
// 使用 MPI_Reduce 将所有进程中的数据项进行求和
MPI_Reduce(&data, &global_sum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
// 根进程输出全局求和结果
if (my_rank == 0) {
printf("Root process received global sum: %d\n", global_sum);
}
MPI_Finalize();
return 0;
}
结果如下:
MPI_Barrier
MPI_Barrier:一种同步操作,用于在通信组中的所有进程之间创建同步点。它确保所有调用该函数的进程都达到了同步点,然后继续执行。
函数原型如下:
int MPI_Barrier(MPI_Comm comm);
comm
:通信组,通常使用 MPI_COMM_WORLD
,表示所有 MPI 进程的默认通信组。
注意:MPI_Barrier
在调用它的所有进程中创建一个同步点。当一个进程调用 MPI_Barrier
时,它会等待所有其他进程也调用了 MPI_Barrier
,然后所有进程同时继续执行。
以下是一个简单的例子,演示 MPI_Barrier
的用法:
//007.c
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(&argc, &argv);
int my_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// 所有进程在此同步点
MPI_Barrier(MPI_COMM_WORLD);
printf("Process %d reached the barrier\n", my_rank);
// 所有进程在此同步点
MPI_Barrier(MPI_COMM_WORLD);
printf("Process %d passed the barrier\n", my_rank);
MPI_Finalize();
return 0;
}