高性能计算导论HPC实验二 MPI并行编程

本文详细介绍了MPI编程环境的设置,包括MPICH2的Windows安装、环境变量配置,以及Blocking和Non-blockingSend/Receive、CollectiveCommunication(如Broadcast和Reduce)的概念和应用。还对比了MPI与MPI+OMP在求解Pi值时的性能差异。
摘要由CSDN通过智能技术生成

目录

一、environment setup for MPI programming

二、MPI Environment

三、blocking Send/Receive

四、nonblocking send/receive

五、collective communication broadcast/reduce pi estimation in MPI and MPI+OMP


一、environment setup for MPI programming

安装mpich:首先在课程平台下载mpich的安装包“MPICH2 for Win64 (mpich2-1.4.1p1-win-x86-64.msi)”。以管理员的身份打开命令提示符,进入文件安装包所在目录后使用指令“msiexec /package mpich2-1.4.1p1-win-x86-64.msi”,安装mpich。安装完毕后,添加目录下的bin文件所在路径到电脑的环境变量中:右键“我的电脑”à选择“属性”à选择“高级系统设置”à“环境变量”——将bin文件路径复制到系统变量和环境变量的path目录下,如果已经存在,不需要复制,不存在的要新建并添加路径。使用带有管理员身份的电脑账户通过cmd运行注册文件,输入电脑账号密码。最后输入“mpiexec -validate” ,验证是否安装成功,显示“it should return SUCCESS”,则意味着安装成功。

验证测试mpich:以管理员的身份运行wmpiexec窗口,选择examples目录下自带的cpi.exe文件进行测试,点击execute开始执行,显示输出“Enter the number of intervals: (0 quits) “则成功。

在vs2022配置mpich:打开创建好的项目,点击项目à属性àC/C++à附加包含目录à添加安装mpich的路径下的include目录。项目à属性à链接器à常规à附加库目录à添加安装的mpich路径下的lib文件夹目录。项目à属性à链接器à输入à附加依赖项à添加mpi.lib

MPI (Message Passing Interface) 是一种并行计算中常用的编程模型,而 MPICH2 是其中一个开源实现。它利用的是Shared distributed memory,即每个存储空间的地址都是0开始的,有多个存储空间,每个memory离每个cpu的距离都不一样的近,每个cpu都可以调用所有的memory。而上一次使用的openMP则是Non-shared-memory,即一个存储空间,地址从零开始后面地址连续,内容不对每个线程共享。

在 Windows 系统上安装MPICH2的过程中需要管理员权限,否则将安装失败,这是因为安装过程中需要进行系统设置和文件访问等操作,而这些操作需要管理员权限才能完成,比如配置系统环境变量和安装MPICH2相关的服务和驱动程序。

二、MPI Environment

1.	#include<stdio.h>  
2.	#include"mpi.h"  
3.	int main()  
4.	{  
5.	    int size, rank, namelength;  
6.	    char name[MPI_MAX_PROCESSOR_NAME];  
7.	    MPI_Init(NULL, NULL);  
8.	    MPI_Comm_size(MPI_COMM_WORLD, &size);  
9.	    MPI_Comm_rank(MPI_COMM_WORLD, &rank);  
10.	    MPI_Get_processor_name(name, &namelength);  
11.	    printf("size=%d rank=%d name=%s len=%d \n", size, rank, name, namelength);  
12.	    printf("myid=%d\n", rank);  
13.	    fflush(stdout);  
14.	    MPI_Finalize();  
15.	    return 0;  
16.	}  

任何一个MPI程序在调用MPI函数之前,首先调用的是初始化函数MPI_Init(int *argc, char ***argv),以建立MPU的执行环境;MPI_Comm_size(MPI_Comm, int *size)函数可以获取通信域包含的进程数;MPI_Comm_rank(MPI_Comm comm, int *rank)函数可以获取当前进程标识;同时MPI程序的最后一个调用必须为:MPI_Finalize()函数以结束MPI程序的执行。

在配置好执行mpi程序后,输出显示当前mpi程序的运行环境。在本机vs中执行时默认线程数为1,所以输出显示为通信域包含线程数1,当前进程标号为0,当前电脑用户名及名字长度,和myid(即当前执行本条指令的进程号)。而在华为泰山服务器中运行时,由于运行时可以直接设置线程数,当设置为4时,4个线程并行执行程序一次,输出四次通信域进程数4,分别输出当前进程标号0-3,服务器名kunpeng191及长度10,和myid。

三、blocking Send/Receive

1.	#include<stdio.h>  
2.	#include"mpi.h"  
3.	int main()  
4.	{  
5.	    MPI_Status status;  
6.	    char string[] = "xxxxx";  
7.	    char str[] = "HELLO";  
8.	    int myid;  
9.	    MPI_Init(NULL, NULL);  
10.	    MPI_Comm_rank(MPI_COMM_WORLD, &myid);  
11.	    printf("myid=%d\n", myid);  
12.	    if (myid == 2)  
13.	        MPI_Send(str, 5, MPI_CHAR, 7, 1234, MPI_COMM_WORLD);  
14.	    if (myid == 7) {  
15.	        MPI_Recv(string, 5, MPI_CHAR, 2, MPI_ANY_TAG, MPI_COMM_WORLD, &status);  
16.	        printf("Got %s from P%d, tag %d\n", string, status.MPI_SOURCE, status.MPI_TAG);  
17.	        fflush(stdout);  
18.	    }  
19.	    MPI_Finalize();  
20.	    return 0;  
21.	} 

函数MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)(buf:发送缓存的起始地址、count:发送的数据个数、datatype:数据类型、dest:目的进程标识号、Tag:消息标志、comm:通信域)可以令源进程将缓存中的数据发送到目的进程;而函数MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)(buf:接收缓存的起始地址、count:最多可接收的数据个数、datatype:数据类型、source:发送数据的进程标识号、Tag:消息标志、comm:通信域、Status:返回状态)可以使得目的进程从源进程接受数据,存入缓存中。

这段程序代码在第二个进程中将“HELLO”作为数据发送到第七个进程中。由于本机执行时默认为1个线程工作,所以没有进行数据的收发,而在服务器上指定16线程工作时,第二进程向第七进程发送,在第七进程接受数据并输出。

四、nonblocking send/receive

1.	#include<stdio.h>  
2.	#include"mpi.h"  
3.	#define N 100000  
4.	int main()  
5.	{  
6.	    int numtasks, rank, dest, source, tag = 1234;  
7.	    char inmsg[] = "xxxxx", outmsg[] = "HELLO";  
8.	    MPI_Status stats[2];  
9.	    MPI_Request reqs[2];  
10.	    MPI_Init(NULL, NULL);  
11.	    MPI_Comm_size(MPI_COMM_WORLD, &numtasks);  
12.	    MPI_Comm_rank(MPI_COMM_WORLD, &rank);  
13.	    if (rank == 0){  
14.	        dest = 1;  
15.	        MPI_Isend(&outmsg, 5, MPI_CHAR, dest, tag, MPI_COMM_WORLD, &reqs[0]);  
16.	        printf("Task %d: Send %s while inmas=%s \n", rank, outmsg, inmsg);  
17.	        fflush(stdout);  
18.	        MPI_Wait(&reqs[0], &stats[0]);  
19.	        printf("Task %d: send %s while inmsg=%s reqs[0]=%d \n", rank, outmsg, inmsg, reqs[0]);  
20.	        fflush(stdout);  
21.	    }  
22.	    else if (rank == 1) {  
23.	        source = 0;  
24.	        MPI_Irecv(&inmsg, 5, MPI_CHAR, source, tag, MPI_COMM_WORLD, &reqs[1]);  
25.	        printf("Task %d: Received %s \n", rank, inmsg);  
26.	        fflush(stdout);  
27.	        MPI_Wait(&reqs[1], &stats[1]);  
28.	        printf("Task %d: Received %s reqs[1]=%d\n", rank, inmsg, reqs[1]);  
29.	        fflush(stdout);  
30.	    }  
31.	    MPI_Finalize();  
32.	    return 0;  
33.	}  

Mpi程序在进行数据的收发时,在成功进行数据接受前,线程将不会进行其他的工作,此时线程的资源将会被部分浪费,在这种情况下,可以使用非阻塞通信来重叠计算和通信,从而使应用程序更高效。使用函数MPI_Isend (*buf,  count, datatype, destination, tag, comm, MPI_Request *request)进行发送数据,并使用函数MPI_Irecv(*buf,  count, datatype, source, tag, comm, MPI_Request *request)接受数据,而如果MPI未实现进度线程,则在调用函数MPI_Wait(MPI_Request *request, MPI_Status *status)之前将不会收到任何消息。

非阻塞式的通信,使得发送和计算可以重叠,发送被接受即刻计算,节约资源,最大化利用线程资源。

五、collective communication broadcast/reduce pi estimation in MPI and MPI+OMP

//MPI
1.	#include<stdio.h>  
2.	#include"mpi.h"  
3.	#define N 100000  
4.	int main()  
5.	{  
6.	    int myid, numproces, i, n;  
7.	    double mypi, pi, h, sum, x, start, end;  
8.	    n = N;  
9.	    MPI_Init(NULL, NULL);  
10.	    MPI_Comm_size(MPI_COMM_WORLD, &numproces);  
11.	    MPI_Comm_rank(MPI_COMM_WORLD, &myid);  
12.	    MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);  
13.	    h = 1.0 / n;  
14.	    sum = 0.0;  
15.	    start = MPI_Wtime();  
16.	    for (i = myid + 1; i <= N; i += numproces) {  
17.	        x = h * ((double)i - 0.5);  
18.	        sum += (4.0 / (1.0 + x * x));  
19.	    }  
20.	    mypi = h * sum;  
21.	    MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);  
22.	    if (myid == 0) {  
23.	        end = MPI_Wtime();  
24.	        printf("pi is approximately %.16f\n", pi);  
25.	        printf("time is %.16f\n", end - start);  
26.	        fflush(stdout);  
27.	    }  
28.	    MPI_Finalize();  
29.	  
30.	    return 0;  
31.	}  
//MPI+OMP
1.	#include<stdio.h>  
2.	#include<omp.h>  
3.	#include"mpi.h"  
4.	#define N 100000  
5.	#define NUM_THREADS 16  
6.	int main()  
7.	{  
8.	    int myr, numprocs, i, myn, fi, li, n;  
9.	    double my_pi = 0.0, pi, h, x, mypi, start, end;  
10.	    MPI_Init(NULL, NULL);  
11.	    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);  
12.	    MPI_Comm_rank(MPI_COMM_WORLD, &myr);  
13.	    start = MPI_Wtime();  
14.	    n = N;  
15.	    h = 1.0 / n;  
16.	    myn = n / numprocs;  
17.	    fi = myr * myn;  
18.	    li = fi + myn;  
19.	    omp_set_num_threads(NUM_THREADS);  
20.	#pragma omp parallel for reduction(+:my_pi)private(x,i)  
21.	    for (i = fi; i < li; i++)  
22.	    {  
23.	        x = (i + 0.5) * h;  
24.	        my_pi = my_pi + 4.0 / (1.0 + x * x);  
25.	    }  
26.	    MPI_Reduce(&my_pi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);  
27.	    if (myr == 0)  
28.	    {  
29.	        mypi = h * pi;  
30.	        end = MPI_Wtime();  
31.	        printf("pi is approximately %.16f\n", mypi);  
32.	        printf("time is %.16f\n", end - start);  
33.	        fflush(stdout);  
34.	    }  
35.	    MPI_Finalize();  
36.	    return 0;  
37.	}  

函数MPI_Bcast(void* buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm)(buffer:通信消息缓存的起始地址、count:数据个数、datatype:数据类型、root:根进程的标识号、信息源进程标识号、comm:通信域)可以将根进程的消息广播到组中的所有其他进程,实现对于所有进程中的某数据的值更新,其既是发送方调用,也是接收方调用。函数MPI_Reduce(void* sendbuf, void* recvbuf, int count, MPI_Datatype, MPI_Op op, int root, MPI_Comm comm)(sendbuf:发送消息缓存的起始地址、recvbuf:接收消息缓存的起始地址、count:发送缓存中的数据个数、op:归约操作符、root:根进程序列号、comm:通信域)可以将每个进程缓冲区中的数据按给定的操作进行计算,并将计算结果返回到根进程的输出缓冲区中。

可以使用函数MPI_Wtime()来获取并计算程序运行时间。其返回值为double类型数据。

MPI中预置的归约操作符号如下图:(用户也可以使用MPI_Op_create自定义归约操作)

服务器中无法使用函数omp_set_num_threads来设定工作线程数,将会出现如下图所示报错。

服务器中的工作线程数可以直接通过执行编译后的文件时,使用的指令来修改,如mpiexec -n 16 ./mpiomp.out,其中的16即为线程数。

实验过程中发现两台服务器并行执行使用MPI求pi值的程序时,时间并无明显变化,原因应该为运算时间较短,无明显差距。

使用MPI+OMP的程序求pi值时,程序运行时间大于单独使用MPI求pi值,两种并行计算库的同步使用在较为简单的求取pi值上无明显优势,多台服务器并行执行MPI+OMP程序也无速度的明显提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值