丁致宇第三周学习报告:MPI学习
[TOC]
MPI基础
一个基本的MPI程序框架
#include <mpi.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
// 初始化MPI环境
MPI_Init(&argc, &argv);
// 获取当前进程的排名
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
// 获取总进程数
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
// 让每个进程打印出它的排名和总的进程数
printf("Hello world from rank %d out of %d processors\n", world_rank, world_size);
// 清理MPI环境
MPI_Finalize();
return 0;
}
计时框架
#include <stdio.h>
#include <mpi.h>
int main(int argc, char *argv[])
{
MPI_Init(&argc, &argv);
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
int size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
double start_time = MPI_Wtime();
// ... 在这里执行你的并行代码 ...
// ... 在这里执行你的并行代码 ...
double end_time = MPI_Wtime();
double elapsed_time = end_time - start_time;
// 在所有进程中找到最大的运行时间
double max_elapsed_time;
MPI_Reduce(&elapsed_time, &max_elapsed_time, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
// 在主进程中打印最大运行时间
if (rank == 0)
{
printf("\ntime:\n");
printf("Max elapsed time: %f seconds\n", max_elapsed_time);
}
MPI_Finalize();
return 0;
}
编译运行
要编译和运行这个MPI程序,你需要安装MPI库并使用支持MPI的编译器,比如mpicc。编译命令可能如下所示:
mpicc -o mpi_hello_world mpi_hello_world.c
运行MPI程序时,你需要使用mpirun或mpiexec命令,并指定进程数,例如:
mpirun -np 4 ./mpi_hello_world
这条命令会启动4 个进程运行你的程序。每个进程都会打印出它的排名和总进程数,但是打印的顺序可能是不确定的,因为它们是并行运行的。
点对点通信
MPI(Message Passing Interface)是一个通信协议,用于编程在各个不同节点上运行的并行计算机之间的进程通信。它被设计用来在分布式内存系统上工作,这样的系统通常没有全局地址空间。在这样的系统中,进程间的通信需要通过发送和接收消息来实现。
MPI_Send 函数
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:通信器(communicator),定义了一个进程组和它们之间的通信上下文。
MPI_Recv 函数
MPI_Recv 是 MPI 中用于接收消息的基础函数。它的原型如下:
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)。tag:消息的标签,与发送时的标签相对应。comm:通信器。status:一个结构体,用于返回关于接收到的消息的信息,如源排名、标签和错 误码。
示例
下面是一个简单的 MPI 程序示例,其中使用了 MPI_Send 和 MPI_Recv 函数来在两个进程之间传递一个整数消息。
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
// 初始化 MPI 环境
MPI_Init(&argc, &argv);
// 获取总的进程数
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
// 获取当前进程的排名
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int number;
if (world_rank == 0) {
// 如果是排名为 0 的进程,则发送一个整数到排名为 1 的进程
number = -1;
MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (world_rank == 1) {
// 如果是排名为 1 的进程,则从排名为 0 的进程接收一个整数
MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process 1 received number %d from process 0\n", number);
}
// 清理 MPI 环境
MPI_Finalize();
return 0;
}
在这个例子中,进程 0 发送一个整数(-1)给进程 1。进程 1 接收这个整数并打印出来。请注意,这段代码需要在支持 MPI 的环境中编译和运行,通常使用如下命令:
mpicc -o mpi_example mpi_example.c
mpirun -np 2 ./mpi_example
这里,mpicc 是用于编译 MPI 程序的编译器,而 mpirun 用于启动 MPI 程序,-np 2 指定了使用两个进程。
注意
Send、Recv函数中的buf参数可以不同吗?
在MPI中,MPI_Send和MPI_Recv函数用于在进程间发送和接收消息。这些函数中的buf参数指的是发送和接收数据的缓冲区。在发送和接收操作中,缓冲区的数据类型和数量应该匹配,但实际的缓冲区指针(即内存地址)是可以不同的。
这里是一个简单的说明:
- 发送方 (
MPI_Send):buf参数是指向发送缓冲区的指针,这个缓冲区包含了要发送的数据。 - 接收方 (
MPI_Recv):buf参数是指向接收缓冲区的指针,这个缓冲区用于存储接收到的数据。
发送方和接收方的buf参数通常指向它们各自进程中的不同内存位置。这是因为在不同的进程中,内存空间是隔离的,所以即便是相同的相对地址,在不同的进程中也可能映射到不同的物理内存。
确保通信正确进行的关键在于buf参数所指向的缓冲区大小和数据类型应该与在MPI_Send和MPI_Recv调用中指定的count和datatype参数相匹配。如果发送和接收操作的数据类型或数量不匹配,可能会导致数据错误或程序崩溃。
这里有一个简单的例子:
发 送方:
int send_data[10]; // 发送缓冲区
MPI_Send(send_data, 10, MPI_INT, dest, tag, MPI_COMM_WORLD);
接收方:
int recv_data[10]; // 接收缓冲区
MPI_Recv(recv_data, 10, MPI_INT, source, tag, MPI_COMM_WORLD, &status);
在这个例子中,发送方有一个名为send_data的数组,接收方有一个名为recv_data