MPI分布式内存编程(一)

分布式内存编程与MPI简介

分布式内存系统:由网络连接的核-内存对的集合组成,与核相关联的内存只能由该核访问。

使用消息传递的分布式内存系统编程:运行一个核-内存对的程序为一个进程。两个进程之间分别调用发送函数与接收函数实现消息传递。使用的函数为消息传递接口(Message-Passing Interface, MPI)

Win10+VS2017上配置MPI并行编程环境

参考教程:如何在win10+vs2013上配置MPI并行编程环境

与VS2013唯一的区别在于新建项目时选择的项目选项不同

MPI分布式内存编程(一)_第1张图片

MPI基本函数详解

首先给出一段示例程序

#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
const int MAX_STRING = 100;
int main()
{
	char greeting[MAX_STRING];
	int comm_sz;
	int my_rank;

	MPI_Init(NULL, NULL);
	MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
	MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
	if (my_rank != 0)
	{
		sprintf(greeting, "Greetings from process %d of %d!", my_rank, comm_sz);
		MPI_Send(greeting, strlen(greeting) + 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
	}
	else
	{
		printf("Greetings from process %d of %d!\n", my_rank, comm_sz);
		for (int q = 1; q < comm_sz; q++)
		{
			MPI_Recv(greeting, MAX_STRING, MPI_CHAR, q, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
			printf("%s\n", greeting);
		}
	}
	MPI_Finalize();
	return 0;
}

VS2017下编译该程序,并在Windows PowerShell下运行该程序,最后的路径名是生成可执行文件的路径名

mpiexec -n 4 D:\project\VS2017\Helloworld_MPI\x64\Debug\Helloworld_MPI.exe

执行成功输出的结果

Greetings from process 0 of 4!
Greetings from process 1 of 4!
Greetings from process 2 of 4!
Greetings from process 3 of 4!

上面程序执行的操作为,有4个进程,每个进程向0号进程发送一个消息,在0号进程中打印出来,0号进程本身也打印一条语句。

MPI基本函数、类型、宏和常量

由上面程序可以看出,MPI头文件中包括的标识符均以MPI_开始。下划线后面首字母大写的,表示函数名和类型名;下划线后全部大写的,表示宏或常量

MPI_Init & MPI_Finalize

MPI_Init:告知MPI系统初始化设置

int MPI_Init(
    int* argc_p,
    char*** argv_p
);

两参数分别是指向main函数的两个参数argc和argv的指针,main函数未使用这两个参数时,两者均为NULL即可。
返回值为int型错误码,可以不用
MPI_Finalize:告知MPI系统使用结束

int MPI_Finalize(void);

故MPI程序的基本框架为

#include
int main(int argc, char* argv[])
{
	...
	// No MPI calls before this
	MPI_Init(&argc, &argv);
	...
	MPI_Finalize();
	// No MPI calls after this
	...
}

通信子与MPI_Comm_size & MPI_Comm_rank

通信子:MPI系统中可以互相发送信息的进程集合,在执行初始化函数MPI_Init()时,会定义用户启动的所有进程组成的通信子,称为MPI_COMM_WORLD

MPI中为通信子定义了一个特殊的类型,MPI_Comm

MPI_Comm_size:获取通信子中的进程数

int MPI_Comm_size(
    MPI_Comm comm,
    int* comm_sz_p
);

函数通过输入参数中的comm获取通信子中的进程数,在comm_sz_p中返回

MPI_Comm_rank:获取正在调用进程在通信子中的进程号

int MPI_Comm_rank(
    MPI_Comm comm,
    int* my_rank_p
);

函数通过输入参数中的comm获取当前调用的进程在通信子中的进程号,在my_rank_p中返回

SPMD程序

为了让编译过程只编译一个程序,但不同进程执行的效果不同。我们采用单程序多数据流(Single Program, Multiple Data, SPMD)的方式编程

上面的程序中就采用if-else的形式,通过判断进程号加以区分

MPI_Send

用于消息的发送

int MPI_Send(
    void* msg_buf_p,
    int msg_size,
    MPI_Datatype msg_type,
    int dest,
    int tag,
    MPI_Comm communicator
);

前三个参数用于描述消息,分别是

  • msg_buf_p:消息的起始地址
  • msg_size:发送的数据量
  • msg_type:数据类型

在数据类型上,MPI有一套自己的MPI数据类型,用一个特殊的数据类型MPI_Datatype以及相配套的一组常量,用于表示C语言中的各种类型,比如:char → \to MPI_CHAR;int → \to MPI_INT;double → \to MPI_DOUBLE

  • 第四个参数dest表示接收信息进程的进程号

  • 第五个参数tag为一个非负int型参数,用于给接收端的进程区分信息,比如接收端接收到信息后可能需要做不同的处理,可以在tag参数中进行不同的标号(0,1,…),接收端根据标号进行不同的处理

  • 第六个参数communicator为通信子,使得进程间的通信只能在同一个通信子中进行,避免信息传输的混乱。

MPI_Recv

用于消息的接收

int MPI_Recv(
    void* msg_buf_p,
    int buf_size,
    MPI_Datatype buf_type,
    int source,
    int tag,
    MPI_Comm communicator,
    MPI_Status* status_p
);

前六个参数与MPI_Send的参数相对应。

  • 前三个参数用于描述消息,内存起始地址,长度,数据类型
  • 第四个参数source,表示消息来源进程号
  • 第五个参数tag必须与发送信息的参数tag相匹配
  • 第六个参数communicator与发送进程的通信子相匹配
  • 第七个参数status_p,消息状态,接收函数返回时,将在这个参数指示的变量中存放实际接收消息的状态信息。大部分情况下不使用,赋予MPI常量MPI_STATUS_IGNORE即可,具体使用见后面

两进程能够成功完成通信,发送进程的MPI_Send函数与接收进程MPI_Recv的函数匹配条件

  • 两函数对应接收(发送)进程号对应
  • 两函数tag相匹配
  • 两函数通信子相匹配
  • 前三个参数所指定的消息缓冲区兼容,简单来讲就是数据类型兼容,且发送方消息长度小于接收方消息长度

由于不同进程完成的时间可能不同,接收方为了按照发送进程结束的时间接收信号,可在MPI_Recv函数的source参数中使用MPI_ANY_SOURCE常量

for(int i=0; i

若接收方可限制发送方的tag,可在tag参数中使用MPI_ANY_TAG常量

MPI_Status

在介绍了MPI_ANY_SOURCE和MPI_ANY_TAG两个常量之后,接收消息的进程对于消息来源不再十分清楚,因此需要借助MPI_Recv()函数的最后一项参数,来了解消息来源的进程号,tag,以及消息的长度。

该参数的参数类型为结构体MPI_Status

typedef struct _MPI_Status {
    int count;
    int cancelled;
    int MPI_SOURCE;
    int MPI_TAG;
    int MPI_ERROR;
} MPI_Status, *PMPI_Status;

其中MPI_SOURCE为来源进程号,MPI_TAG为消息的tag。使用之前定义一个MPI_Status型变量,作为参数放入MPI_Recv()函数中。

想要得到消息数据量的大小,则需要调用函数MPI_Get_count()

int MPIAPI MPI_Get_count(
    MPI_Status *status,
    MPI_Datatype datatype,
    int *count
);

输入MPI_Status型变量及数据类型,返回在count参数中,实际使用时返回值为消息的内存占用大小,若想要知道实际该数据类型下的数据量,还需要除以单个该数据类型的字节数。

MPI_Status sts;
int send_rank;
MPI_Recv(&send_rank, sizeof(send_rank), MPI_INT, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &sts);
int count;
MPI_Get_count(&sts, MPI_INT, &count);
printf("count:%d\n", count);
printf("real count:%d\n", count/sizeof(send_rank));

执行结果

count:4
real count:1

参考文献
[1] 并行程序设计导论第三章

你可能感兴趣的:(并行计算)