分布式内存系统:由网络连接的核-内存对的集合组成,与核相关联的内存只能由该核访问。
使用消息传递的分布式内存系统编程:运行一个核-内存对的程序为一个进程。两个进程之间分别调用发送函数与接收函数实现消息传递。使用的函数为消息传递接口(Message-Passing Interface, MPI)
参考教程:如何在win10+vs2013上配置MPI并行编程环境
与VS2013唯一的区别在于新建项目时选择的项目选项不同
首先给出一段示例程序
#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_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系统中可以互相发送信息的进程集合,在执行初始化函数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中返回
为了让编译过程只编译一个程序,但不同进程执行的效果不同。我们采用单程序多数据流(Single Program, Multiple Data, SPMD)的方式编程
上面的程序中就采用if-else的形式,通过判断进程号加以区分
用于消息的发送
int MPI_Send(
void* msg_buf_p,
int msg_size,
MPI_Datatype msg_type,
int dest,
int tag,
MPI_Comm communicator
);
前三个参数用于描述消息,分别是
在数据类型上,MPI有一套自己的MPI数据类型,用一个特殊的数据类型MPI_Datatype以及相配套的一组常量,用于表示C语言中的各种类型,比如:char → \to → MPI_CHAR;int → \to →MPI_INT;double → \to →MPI_DOUBLE
第四个参数dest表示接收信息进程的进程号
第五个参数tag为一个非负int型参数,用于给接收端的进程区分信息,比如接收端接收到信息后可能需要做不同的处理,可以在tag参数中进行不同的标号(0,1,…),接收端根据标号进行不同的处理
第六个参数communicator为通信子,使得进程间的通信只能在同一个通信子中进行,避免信息传输的混乱。
用于消息的接收
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的参数相对应。
两进程能够成功完成通信,发送进程的MPI_Send函数与接收进程MPI_Recv的函数匹配条件
由于不同进程完成的时间可能不同,接收方为了按照发送进程结束的时间接收信号,可在MPI_Recv函数的source参数中使用MPI_ANY_SOURCE常量
for(int i=0; i
若接收方可限制发送方的tag,可在tag参数中使用MPI_ANY_TAG常量
在介绍了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] 并行程序设计导论第三章