- 熟悉Linux环境的MPICH并行计算库
列出当前目录中的所有文件或目录:ls
删除文件或空目录:rm
上传文件:rz
编译:mpicc -g -Wall -o helloc
执行:mpiexec -n 4 ./hello
二.HELLO WORLD并行程序设计
(一)程序源码:
(1)源码:
#include <mpi.h>
#include <stdio.h>
int main(int argc, char** argv) {
MPI_Init(NULL, NULL);
int wsize;//单词长度
MPI_Comm_size(MPI_COMM_WORLD, &wsize);
int wrank;
MPI_Comm_rank(MPI_COMM_WORLD, &wrank);
char processor_name[MPI_MAX_PROCESSOR_NAME];//处理器名称
int nlen;
MPI_Get_processor_name(processor_name, &nlen);
printf("Hello world from processor %s, rank %d out of %d processors\n",
processor_name, wrank, wsize);
MPI_Finalize();
}
(2)源码截屏:
- 关键函数分析:
a. MPI_Init: 初始化MPI环境。此函数需要在使用任何MPI函数之前调用。接受两个参数,可以传入命令行参数,但本例中传入了NULL,因为不需要特殊参数。
b. MPI_Comm_size: 获取通信组中的进程数量。此函数接受两个参数:一个通信器(通常是MPI_COMM_WORLD,表示所有可用的MPI进程)和一个指向整数的指针,用于存储进程数量。在本例中,wsize被设置为进程数量。
c. MPI_Comm_rank: 获取当前进程的等级(唯一标识)。同样接受一个通信器和一个指向整数的指针,用于存储等级。在本例中,wrank被设置为当前进程的等级。
d. MPI_Get_processor_name: 获取处理器名称。它接受一个字符数组和一个指向整数的指针,用于存储名称长度。在本例中,processor_name存储处理器名称,nlen存储名称长度。
e. printf: 标准C库函数,用于输出。在本例中,输出处理器名称、当前进程等级和进程总数。
f. MPI_Finalize: 结束MPI环境。在使用完MPI函数后调用,以清理资源。
- 代码实现的整体逻辑:
本程序是一个简单的MPI程序,用于演示如何使用MPI函数初始化MPI环境、获取通信组中的进程数量、获取当前进程的等级以及处理器名称。程序首先初始化MPI环境,然后获取通信组中的进程数量和当前进程的等级。接着,获取处理器名称并输出相关信息。最后,程序结束MPI环境。
(二)程序编译
mpicc -g -Wall -o hello hello.c
(三)运行结果
mpiexec -n 4 ./hello
- 实验心得:
(1)程序编写技巧:
a. 调用顺序:在使用MPI函数时,需要注意调用顺序。首先需要调用MPI_Init初始化环境,最后调用MPI_Finalize结束环境。在这两个函数之间,可以调用其他MPI函数。
b. 传递指针:注意MPI_Comm_size、MPI_Comm_rank和MPI_Get_processor_name函数需要传递指向整数的指针来获取结果。避免传递错误的指针类型,以免导致未定义行为。
c. 使用MPI_COMM_WORLD:在大多数情况下,我们可以使用MPI_COMM_WORLD作为通信器。这是一个全局通信器,表示所有可用的MPI进程。
d. 并行思维:编写MPI程序时,需要注意程序将在多个进程中并行运行。尽管本例非常简单,但在处理更复杂的问题时,需要考虑如何将问题拆分成子任务,并在进程之间传递数据。
函数使初始化MPI:通过调用MPI_Init(NULL, NULL)函数来初始化MPI。在参数中,使用NULL表示使用默认的命令行参数。
获取进程数量:使用MPI_Comm_size(MPI_COMM_WORLD, &wsize)函数获取通信域MPI_COMM_WORLD中的进程数量,并将结果存储在wsize变量中。
获取进程排名:使用MPI_Comm_rank(MPI_COMM_WORLD, &wrank)函数获取当前进程在通信域MPI_COMM_WORLD中的排名,并将结果存储在wrank变量中。
获取处理器名称:使用MPI_Get_processor_name(processor_name, &nlen)函数获取当前进程的处理器名称,并将结果存储在processor_name数组中。nlen变量用于指定处理器名称的最大长度。
输出信息:使用printf函数将进程的信息输出到标准输出。这里使用了processor_name(处理器名称)、wrank(进程排名)和wsize(进程数量)等变量。
结束MPI:在程序的最后通过调用MPI_Finalize()函数来结束MPI。用技巧:
可能出现问题的地方:
编译和链接MPI库:在编译代码时,需要使用MPI编译器和MPI库。例如,使用mpicc编译器编译代码时,可以使用以下命令:mpicc your_code.c -o executable_name -lmpi。
并行环境设置:在运行该程序时,需要在并行环境中执行,以便多个进程能够正常工作。具体如何设置并行环境取决于使用的MPI实现和集群管理工具。
并行调试:在并行程序中,可能会遇到与进程同步、通信和并发相关的问题。调试并行程序可能比调试串行程序更具挑战性。可以使用MPI提供的调试工具,如mpiexec, mpirun等,以及其他调试技术来帮助解决问题。
命令行参数:该代码示例中使用了MPI_Init(NULL, NULL)来指定使用默认的命令行参数。如果需要在命令行中传递参数给MPI程序,可以修改这部分代码,使用命令行参数进行初始化。
错误处理:MPI函数返回的错误代码可以用于检测并处理MPI调用中的错误。在实际的MPI程序中,通常需要对MPI函数的返回值进行检查,并根据错误码采取相应的错误处理措施。
心得:
在HELLO WORLD并行程序设计的实验中,我初步了解了并行计算和MPI(Message Passing Interface)的概念和使用方法。通过这个实验,我学会了如何使用MPI库中的函数和数据类型,实现多个进程之间的消息传递和同步,以及基本的并行计算任务的分发和处理。
在实现过程中,我遇到了一些问题,例如程序运行时出现错误或崩溃等,这时我通过调试程序和查看MPI函数文档等方式来解决问题。同时,我还学会了如何在命令行终端中使用MPI命令行工具来启动和管理MPI程序。
通过这个实验,我更深入地理解了并行计算和MPI编程的基本原理和技术,也提高了我的编程和调试能力。这将对我的未来学习和研究产生积极的影响。
三.Greetings并行程序设计(非根进程发送消息,根进程接收)
(一)程序源码:
(1)源码:
#include <stdio.h>
#include <string.h>
#include <mpi.h>
const int MAX_STRING = 100;
int main(void) {
char greeting[MAX_STRING];
int sz;
int rank;
MPI_Init(NULL, NULL);
MPI_size(MPI_WORLD, &sz);
MPI_rank(MPI_WORLD, &rank);
if(rank != 0) {
sprintf(greeting, "Greeting from process %d of %d!", rank, sz);
MPI_Send(greeting, strlen(greeting) + 1, MPI_CHAR, 0, 0, MPI_WORLD);
}else {
printf("Greetings from process %d of %d!\n", rank, sz);
for(int i = 1; i < sz; i++) {
MPI_Recv(greeting, MAX_STRING, MPI_CHAR, i, 0, MPI_WORLD, MPI_STATUS_IGNORE);
printf("%s\n", greeting);
}
}
MPI_Finalize();
return 0;
}
(2)源码截屏:
- 关键函数分析:
a. MPI_Init: 初始化MPI环境。此函数需要在使用任何MPI函数之前调用。接受两个参数,可以传入命令行参数,但本例中传入了NULL,因为不需要特殊参数。
b. MPI_size: 获取通信组中的进程数量。此函数接受两个参数:一个通信器(通常是MPI_WORLD)和一个指向整数的指针,用于存储进程数量。在本例中,sz被设置为进程数量。
c. MPI_rank: 获取当前进程的等级(唯一标识)。同样接受一个通信器和一个指向整数的指针,用于存储等级。在本例中,rank被设置为当前进程的等级。
d. sprintf: 标准C库函数,将格式化字符串写入字符数组。在本例中,将进程信息写入greeting字符数组。
e. MPI_Send: 发送消息给其他进程。接受六个参数:要发送的数据缓冲区、数据数量、数据类型、目标进程等级、消息标签和通信器。在本例中,非0等级的进程将greeting消息发送给0等级的进程。
f. MPI_Recv: 接收其他进程发送的消息。接受七个参数:接收缓冲区、数据数量、数据类型、源进程等级、消息标签、通信器和一个状态对象。在本例中,0等级的进程接收其他进程发送的greeting消息。
g. printf: 标准C库函数,用于输出。在本例中,输出进程信息。
h. MPI_Finalize: 结束MPI环境。在使用完MPI函数后调用,以清理资源。
- 代码实现的整体逻辑:
本程序是一个简单的MPI程序,用于演示进程间的通信。程序首先初始化MPI环境,然后获取通信组中的进程数量和当前进程的等级。对于非0等级的进程,它们将进程信息发送给0等级的进程。对于0等级的进程,它首先输出自己的进程信息,然后接收并输出其他进程发送的进程信息。最后,程序结束MPI环境。
(二)程序编译
mpicc -g -Wall -o greeting greeting.c
- 运行结果
mipiexec -n 4 ./greeting
- 实验心得
程序编写技巧:
a. 并行思维:编写MPI程序时,需要注意程序将在多个进程中并行运行。在处理更复杂的问题时,需要考虑如何将问题拆分成子任务,并在进程之间传递数据。
b. 正确使用MPI_Send和MPI_Recv:注意在发送和接收消息时,要保证数据类型、数据数量、源/目标进程等级和消息标签相匹配。不正确的参数可能导致错误的结果或程序阻塞。
c. 注意数组大小:在本例中,字符数组greeting的大小被设置为MAX_STRING,这需要确保足够大以容纳进程信息。如果数组太小,可能会导致缓冲区溢出,从而引发未定义行为。
d. 避免死锁:在使用MPI_Send和MPI_Recv进行进程间通信时,要注意避免死锁的发生。死锁通常发生在多个进程相互等待对方完成操作时。为了避免死锁,可以使用其他的通信模式,如非阻塞通信(MPI_Isend和MPI_Irecv)或集体通信操作(如MPI_Bcast、MPI_Scatter和MPI_Gather)。
e. 使用MPI_STATUS_IGNORE:在使用MPI_Recv时,如果不关心接收到的消息的状态信息,可以使用MPI_STATUS_IGNORE作为状态参数。这可以简化代码,避免不必要的状态变量声明。
f. 掌握MPI常见数据类型:熟悉MPI提供的各种预定义数据类型,如MPI_INT、MPI_FLOAT、MPI_DOUBLE和MPI_CHAR等,以便在发送和接收数据时正确设置数据类型。
g. 保持代码简洁:在编写MPI程序时,尽量保持代码简洁明了,以便于理解和维护。如本例中,通过对非0等级进程和0等级进程分别处理,使得代码逻辑清晰。
通过编写和分析这段代码,可以加深对MPI编程的理解,掌握进程间通信的基本方法,并学习到一些避免常见错误和提高代码质量的技巧。
函数使用技巧
- 初始化MPI:通过调用MPI_Init(NULL, NULL)函数来初始化MPI。在参数中,使用NULL表示使用默认的命令行参数。
- 获取进程数量和排名:使用MPI_Comm_size(MPI_COMM_WORLD, &sz)函数获取通信域MPI_COMM_WORLD中的进程数量,并使用MPI_Comm_rank(MPI_COMM_WORLD, &rank)函数获取当前进程在通信域中的排名。
- 发送消息:在排名不为0的进程中,使用MPI_Send函数将一条包含进程排名的问候消息发送到排名为0的主进程。其中,strlen(greeting) + 1表示消息的长度,MPI_CHAR表示消息的数据类型。
- 接收消息:在主进程(排名为0)中,使用MPI_Recv函数接收从其他进程发送的问候消息。MPI_STATUS_IGNORE表示忽略接收状态。
- 打印消息:在主进程中,使用printf函数将接收到的问候消息打印出来。
- 结束MPI:在程序的最后通过调用MPI_Finalize()函数来结束MPI。
可能出现问题的地方
- 函数名称错误:代码中使用了MPI_size和MPI_rank函数来获取进程数量和排名,正确的函数名称应该是MPI_Comm_size和MPI_Comm_rank。
- MPI_WORLD的正确写法:代码中使用了MPI_WORLD,正确的写法应该是MPI_COMM_WORLD,表示通信域。
- 字符串溢出:在发送消息时,使用strlen(greeting) + 1作为消息的长度。请确保发送的消息不会超过MAX_STRING定义的最大长度。
- 接收消息的循环:在主进程中使用循环来接收其他进程发送的消息。在循环中,使用MPI_Recv函数接收消息,并使用MPI_CHAR作为消息的数据类型。确保接收到足够的消息来避免阻塞。
- 错误处理:MPI函数返回的错误代码可以用于检测并处理MPI调用中的错误。在实际的MPI程序中,通常需要对MPI函数的返回值进行检查,并根据错误码采取相应的错误处理措施。
心得体会
在Greetings并行程序设计的实验中,我主要学习了如何使用MPI(Message Passing Interface)在多个进程之间进行消息传递。这个实验要求非根进程向根进程发送消息,而根进程接收并打印出来。
通过这个实验,我深刻理解了MPI中的通信机制和编程模式,比如点对点通信、广播、归约等。同时,我也学会了如何使用MPI库中的各种函数来完成消息传递和处理,例如MPI_Send和MPI_Recv等函数。
在实现非根进程发送消息和根进程接收消息的过程中,我遇到了一些问题,例如发送和接收的数据类型不匹配、发送的消息大小超过了接收缓冲区的大小等。我解决这些问题的方法是调试程序、查看MPI函数的文档和资料,并根据错误信息进行排除。
总的来说,这个实验让我更加深入地了解了MPI的使用和通信模型,也提高了我的调试技能和问题解决能力。在今后的研究和工作中,我相信这些经验和技能将会对我有很大的帮助。