并行计算(Parallel Computing)是指同时使用多种计算资源解决计算问题的过程,是提高计算机系统计算速度和处理能力的一种有效手段。它的基本思想是用多个处理器来协同求解同一问题,即将被求解的问题分解成若干个部分,各部分均由一个独立的处理机来并行计算。
在win+vs上运行MPI指令
配置就不记录了,网上有很多。
win+vs+MPI,不能像常规程序一样直接运行。
- 先在vs里写好代码
- 重新生成项目
- 找到项目名.exe所在文件夹
- Shift+鼠标右键,打开Powershell窗口
- 输入命令(比如): mpiexec -n 10 HelloWord_MPI.exe
注意:
mpiexec是运行MPI并行程序的指令,基本格式为
mpiexec -n N ./xxxxxx
其中N是并行进程数,xxxxxx是并行程序名称。
MPI
MPI是一个跨语言的通讯协议,用于编写并行计算机。
6个基本函数
理论上讲 MPI 的所有通信功能都可以使用它的6个基本函数来实现。
//初始化
MPI_Init(int *argc,char ***argv)
//MPI 结束调用
MPI_Finalize(void)
/*
*获得进程的标识号
*保存在rank里
*/
MPI_Comm_rank(MPI_Comm comm,int *rank)
/*
*获得当前通信域中进程的个数
*保存在size里
*/
MPI_Comm_size(MPI_Comm comm,int *size)
//发送消息
MPI_Send(Void *buf,int count,MPI_Datatype,int dest,int tag,MPI_Comm comm)
//接受消息
MPI_Recv(void *buf,int count,MPI_Datatype data,int source,int tag,MPI_Comm comm,MPI_Status *status)
消息传递
MPI 通过 MPI_Send 和 MPI_Receive 发送和接受消息。其中这两种属于阻塞通信,对于发送方来说,消息发送不出去就会一直等待,而对于接收方来说,接收不到消息就会一直等待。
MPI_Send 各参数的含义:
buf- 发送缓冲区的起始位置count- 发送的数据个数datatype- 发送数据的数据类型dest- 目标进程标号tag- 消息标志comm- 通信域
MPI_Recv 各参数含义:
buf- 接受缓冲区地址count- 最多可接受的数据个数datatype- 接受的数据类型source- 发送数据的进程号tag- 消息标志comm- 通信域status- 返回状态
发送和接受的时候是以指定的 datatype 为基本单位的,count 是 datatype 类型数据的数目。在接受数据时,接受缓冲区的长度可以大于发送数据的长度。但是 MPI 中没有数据截断,如果发送数据长度大于接受缓冲区的长度就会报错。
在 C 实现中,状态变量 MPI_Status 必须要包含 3 个信息:MPI_SOURCE, MPI_TAG 和 MPI_ERROR,除此之外还可以包含其它的附加域。
详细说明:
MPI_Send:
- void *buff:你要发送的变量。
- int count:你发送的消息的个数(注意:不是长度,例如你要发送一个int整数,这里就填写1,如要是发送“hello”字符串,这里就填写6(C语言中字符串未有一个结束符,需要多一位))。
- MPI_Datatype datatype:你要发送的数据类型,这里需要用MPI定义的数据类型,可在网上找到,在此不再罗列。
- int dest:目的地进程号,你要发送给哪个进程,就填写目的进程的进程号。
- int tag:消息标签,接收方需要有相同的消息标签才能接收该消息。
- MPI_Comm comm:通讯域。表示你要向哪个组发送消息。
MPI_Recv:
- void *buff:你接收到的消息要保存到哪个变量里。
- int count:你接收消息的消息的个数(注意:不是长度,例如你要发送一个int整数,这里就填写1,如要是发送“hello”字符串,这里就填写6(C语言中字符串未有一个结束符,需要多一位))。它是接收数据长度的上界. 具体接收到的数据长度可通过调用MPI_Get_count 函数得到。
- MPI_Datatype datatype:你要接收的数据类型,这里需要用MPI定义的数据类型,可在网上找到,在此不再罗列。
- int source:接收端进程号,你要需要哪个进程接收消息就填写接收进程的进程号。
- int tag:消息标签,需要与发送方的tag值相同的消息标签才能接收该消息。
- MPI_Comm comm:通讯域。
- MPI_Status *status:消息状态。接收函数返回时,将在这个参数指示的变量中存放实际接收消息的状态信息,包括消息的源进程标识,消息标签,包含的数据项个数等。
预定义数据类型
MPI 预定义了下面几种数据类型
MPI预定义数据类型
对应的C数据类型
MPI_CHAR :signed char
MPI_SHORT :signed short int
MPI_INT :signed int
MPI_LONG :signed long int
MPI_LONG_LONG_INT :long long int (optional)
MPI_UNSIGNED_CHAR :unsigned char
MPI_UNSIGNED_SHORT :unsigned short int
MPI_UNSIGNED :unsigned int
MPI_UNSIGNED_LONG :unsigned long int
MPI_FLOAT :float
MPI_DOUBLE :double
MPI_LONG_DOUBLE :long double
MPI_BYTE :无
MPI_PACKED :无
在传递信息的时候,要保证两个方面的类型匹配(除了 MPI_BYTE 和 MPI_PACKED ):
- 传输的数据类型和通信中声明的 MPI 类型要对应,即数据类型为
int, 那么通信时声明的数据类型就要为MPI_INT。 - 发送方和接受方的类型要匹配
MPI_BYTE 和 MPI_PACKED 可以和任意以字节为单位的存储相匹配。 MPI_BYTE 可以用于不加修改的传送内存中的二进制值。
任意源和任意标识
在消息传递时,发送操作必须明确指定发送对象的进程标号和消息标识,但是接收消息时,可以通过使用 MPI_ANY_SOURCE和 MPI_ANY_TAG 来接受任意进程发送给本进程的消息,类似于通配符。MPI_ANY_SOURCE 和 MPI_ANY_TAG 可以同时使用或者分别单独使用。
Hello World
#include <stdio.h>
#include "mpi.h"
int main(int argc,char *argv[]){
int rank;
int size;
MPI_Init( &argc, &argv );
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
printf( "Hello world from process %d of %d\n", rank, size );
MPI_Finalize();
return 0;
}
Hello world from process 0 of 1
举例
1-n号进程发送,0号进程接收
此为标准阻塞接收发送的方式
#include <stdio.h>
#include <string.h>
#include "mpi.h"
void main(int argc, char* argv[])
{
int numprocs, myid, source;
MPI_Status status;
char message[100];
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
if (myid != 0) { //非0号进程发送消息
strcpy(message, "Hello World!");
MPI_Send(message, strlen(message) + 1, MPI_CHAR, 0, 99,
MPI_COMM_WORLD);
}
else { // myid == 0,即0号进程接收消息
for (source = 1; source < numprocs; source++) {
MPI_Recv(message, 100, MPI_CHAR, source, 99,
MPI_COMM_WORLD, &status);
printf("接收到第%d号进程发送的消息:%s\n", source, message);
}
}
MPI_Finalize();
} /* end main */
PS F:\并行程序学习\HelloWord_MPI\X64\Debug> mpiexec -n 4 HelloWord_MPI.exe
接收到第1号进程发送的消息:Hello World!
接收到第2号进程发送的消息:Hello World!
接收到第3号进程发送的消息:Hello World!
PS F:\并行程序学习\HelloWord_MPI\X64\Debug> mpiexec -n 8 HelloWord_MPI.exe
接收到第1号进程发送的消息:Hello World!
接收到第2号进程发送的消息:Hello World!
接收到第3号进程发送的消息:Hello World!
接收到第4号进程发送的消息:Hello World!
接收到第5号进程发送的消息:Hello World!
接收到第6号进程发送的消息:Hello World!
接收到第7号进程发送的消息:Hello World!
可以看到,当开启四线程运行时,1-3号进程发送消息,0号进程接收到消息并打印;当开启八线程运行时,1-7号进程发送消息,0号进程接收到消息并打印。
线程依次传参
#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"
/**
* 数据传递
*
* 线程 i 向线程 i+1 传数据
*/
int main() {
int param;
int process_num;
int process_id;
MPI_Status status;
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &process_id);
MPI_Comm_size(MPI_COMM_WORLD, &process_num);
if(process_id == 0) {
param = 3;
printf("rank 0 send rank 1: %d\n", param);
param++;
MPI_Send(¶m, 1, MPI_INT, 1, 99, MPI_COMM_WORLD);
} else {
MPI_Recv(¶m, 1, MPI_INT, process_id - 1, 99, MPI_COMM_WORLD, &status);
printf("rank %d receive from %d: %d\n",process_id, process_id-1, param);
if(process_id < process_num -1) {
param++;
printf("rank %d send rank %d: %d\n", process_id, process_id+1, param);
MPI_Send(¶m, 1, MPI_INT, process_id + 1, 99, MPI_COMM_WORLD);
}
}
MPI_Finalize();
}
PS F:\并行程序学习\HelloWord_MPI\X64\Debug> mpiexec -n 4 HelloWord_MPI.exe
rank 1 receive from 0: 4
rank 1 send rank 2: 5
rank 2 receive from 1: 5
rank 2 send rank 3: 6
rank 0 send rank 1: 3
rank 3 receive from 2: 6
通信类别
对于 MPI 中的通信中,从通信对象方面看可以分为两类:
- 点对点通信 - 只涉及发送方和接收方两个进程,并且发送和接收时要指明发送和接收的对象。在发送进程和接收进程里,两者的调用的函数也不相同。
- 组通信 - 一个特定组内的所有进程同时参与通信,并且组通信在每个进程中的调用方式完全一样。
对于点对点通信来讲,从发送行为不同又可以分为下面三类:
- 阻塞通信 - 在发送和接受操作完成之前,程序一直处于等待状态
- 非阻塞通信 - 无需等待发送和操作行为完成就可以返回,然后再调用特定的方法判断通信操作是否完成
- 重复非阻塞通信 - 针对一个通信被多次调用的情况(例如循环结构内的通信调用),重复利用通信对象,而不是每次都开启一个新的通信。单次的通信和非阻塞通信的行为相同。
对于组通信来说,会为组内的进程隐式添加一个同步点,等到所有进程到达后再往下执行。在后面的文章我们会详细介绍上面提到的通信类别。
>> Home