CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
转载自超算习堂的在线实训。
MPI简介
MPI,全称Message Passing Interface(消息传递接口),是业界定义的一种消息传递标准,用于编写并行计算的程序,广泛应用于高性能计算领域。
标准定义了库函数核心的基本语法和语义,在C, C++和Fortran语言中也可以写出具有消息传递功能的程序。
MPI标准有许多经过良好测试且效率较高的的实现,这些实验是开源的,可供公众使用。
配置MPI环境
为了正常编译MPI代码,需要安装C, C++与Fortran的编译环境。
Ubuntu缺省情况下,并没有提供这些语言的编译环境,因此需要手动安装。如果单独安装这些编译环境非常麻烦。幸运的是,build-essential工具提供了许多与编译相关的软件包,包括gcc/g++/gfortran等编译器、libc6-dev等必要的库与其他工具。于是,我们只需要通过包管理器安装build-essential即可。
MPICH是MPI标准的一种重要的实现,可以免费从网上下载。MPICH的开发与MPI规范的制订是同步进行的,因此MPICH最能反映MPI的变化和发展。MPICH是MPI最流行的非专利实现,由Argonne国家实验室和密西西比州立大学联合开发,具有更好的可移植性,现阶段多流行的是MPICH2。
apt命令的 -y 选项默认安装过程中同意所有的默认选择。
1
sudo apt install -y build-essential mpich
第一个MPI程序
首先,我们应该先包含进一个头文件<mpi.h>
,我们使用的函数都在其中。另外,在这之后,MPI程序和普通的C程序的区别在于有一个开始的函数和结束的函数来标识MPI部分,再在这个部分进行你想要进行的操作,现在就来尝试一下!
以下是第一段程序helloworld.c
的内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
int MPI_Init(int *argc, char **argv);//通过MPI_Init函数进入MPI环境并完成所有的初始化工作,标志并行代码的开始。
int MPI_Finalize(void);//通过MPI_Finalize函数从MPI环境中退出,标志并行代码的结束,如果不是MPI程序最后一条可执行语句,则运行结果不可知。
*/
#include <mpi.h>
#include <stdio.h>
int main(int argc, char **argv)
{
//your code here
MPI_Init(&argc, &argv);
puts("Hello World!");
MPI_Finalize();
//end of your code
return 0;
}
按下面的指令编译运行(以下都以四个进程执行为例)。
1
2
mpicc helloworld.c -o helloworld
mpirun -np 4 ./mpihelloworld
得到输出。
1
2
3
4
Hello World!
Hello World!
Hello World!
Hello World!
获取进程数量
在MPI编程中,我们常常需要获取指定通信域的进程个数,以确定程序的规模。
一组可以相互发送消息的进程集合叫做通信子,通常由MPI_Init()在用户启动程序时,定义由用户启动的所有进程所组成的通信子,缺省值为 MPI_COMM_WORLD 。这个参数是MPI通信操作函数中必不可少的参数,用于限定参加通信的进程的范围。
使用函数MPI_Comm_size获取通信域中的进程个数并打印出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
int MPI_Comm_size(MPI_Comm comm, int *rank);//获取指定通信域的进程个数。其中,第一个参数是通信子,第二个参数返回进程的个数
*/
#include <stdio.h>
#include <mpi.h>
int main(int argc, char **argv)
{
int numprocs;
MPI_Init(&argc, &argv);
//your code here
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
//end of your code
printf("Hello World! The number of processes is %d\n",numprocs);
MPI_Finalize();
return 0;
}
/*
Hello World! The number of processes is 4
Hello World! The number of processes is 4
Hello World! The number of processes is 4
Hello World! The number of processes is 4
*/
获取进程id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
int MPI_Comm_rank(MPI_Comm comm, int *rank);//获得当前进程在指定通信域中的编号,将自身与其他程序区分。其中,第一个参数是通信子,第二个参数返回进程的编号。
*/
#include <stdio.h>
#include <mpi.h>
int main(int argc, char **argv)
{
int myid, numprocs;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
//your code here
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
//end of your code
printf("Hello World!I'm rank %d of %d\n", myid, numprocs);
MPI_Finalize();
return 0;
}
/*
Hello World!I'm rank 3 of 4
Hello World!I'm rank 0 of 4
Hello World!I'm rank 1 of 4
Hello World!I'm rank 2 of 4
*/
获取处理器名
有时候在实际处理中我们可能需要将进程迁移至不同的处理器,而MPI提供了获取处理器名的函数以简单地允许这种行为。
注意在MPI中不需要定义这种迁移。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
int MPI_Get_processor_name ( char *name, int *resultlen);//实际节点的唯一说明字;在name中返回结果的长度;
*/
#include <stdio.h>
#include <mpi.h>
int main(int argc, char **argv)
{
int len;
char name[MPI_MAX_PROCESSOR_NAME];
MPI_Init(&argc, &argv);
//your code here
MPI_Get_processor_name (name, &len);
//end of your code
printf("Hello, world. I am %s.\n", name);
MPI_Finalize();
return 0;
}
/*
Hello, world. I am 60e876622717.
Hello, world. I am 60e876622717.
Hello, world. I am 60e876622717.
Hello, world. I am 60e876622717.
*/
运行时间
在实际编程中,计时是一个很实用的功能。 在MPI编程我们可以使用MPI_Wtime函数在并行代码中计算运行时间,用MPI_Wtick来查看精度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
double MPI_Wtime();//返回一个用浮点数表示的秒数, 它表示从过去某一时刻到调用时刻所经历的时间
double MPI_Wtick();//返回MPI_WTIME的精度,单位是秒,可以认为是一个时钟滴答所占用的时间
*/
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv)
{
int myid, numprocs;
double start, finish;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
//your code here
start=MPI_Wtime();
printf("The precision is: %.9lf\n",MPI_Wtick());
finish=MPI_Wtime();
//your code here
printf("Hello World!I'm rank %d of %d, running %f seconds.\n", myid, numprocs, finish-start);
MPI_Finalize();
return 0;
}
/*
The precision is: 0.000000001
Hello World!I'm rank 3 of 4, running 0.000031 seconds.
The precision is: 0.000000001
Hello World!I'm rank 0 of 4, running 0.000019 seconds.
The precision is: 0.000000001
Hello World!I'm rank 1 of 4, running 0.000026 seconds.
The precision is: 0.000000001
Hello World!I'm rank 2 of 4, running 0.000022 seconds.
*/
同步
在实际工作中,我们常常会因为许多原因需要进行同步操作。 例如,希望保证所有进程中并行代码在某个地方同时开始运行,或者在某个函数调用结束之前不能返回。 这时候我们就需要使用到MPI_Barrier函数。
在此示例程序中,可能是否调用函数不影响最终输出,但这并不意味着效果相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
int MPI_Barrier(MPI_Comm comm);//阻止调用直到communicator中所有进程已经完成调用,就是说,任意一次进程的调用只能在所有communicator中的成员已经开始调用之后进行。
*/
#include<stdio.h>
#include<mpi.h>
int main(int argc, char **argv)
{
int myid, numprocs;
double start, finish;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
//your code here
MPI_Barrier(MPI_COMM_WORLD);
//end of your code
start = MPI_Wtime();
printf("The precision is: %f\n", MPI_Wtick());
finish = MPI_Wtime();
printf("Hello World!I'm rank %d of %d, running %f seconds.\n", myid, numprocs, finish-start);
MPI_Finalize();
return 0;
}
/*
The precision is: 0.000000
Hello World!I'm rank 0 of 4, running 0.000023 seconds.
The precision is: 0.000000
Hello World!I'm rank 1 of 4, running 0.000016 seconds.
The precision is: 0.000000
Hello World!I'm rank 2 of 4, running 0.000017 seconds.
The precision is: 0.000000
Hello World!I'm rank 3 of 4, running 0.000015 seconds.
*/
消息传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*
int MPI_Send(
void *msg_buf_p,//发送缓冲区的起始地址;
int msg_size,//缓冲区大小
MPI_Datatype msg_type,//发送信息的数据类型
int dest,//目标进程的id值
int tag,//消息标签
MPI_Comm communicator);//通信子
int MPI_Recv(
void *msg_buf_p,//缓冲区的起始地址;
int buf_size,//缓冲区大小;
MPI_Datatype msg_type,//发送信息的数据类型;
int source,//目标进程的id值;
int tag,//消息标签;
MPI_Comm communicator,//通信子;
MPI_Status *status_p);//status_p对象,包含实际接收到的消息的有关信息
*/
#include <stdio.h>
#include <string.h>
#include <mpi.h>
int main(int argc, char **argv)
{
int myid, numprocs, 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) {
strcpy(message, "hello world!");
//your code here
MPI_Send(message, strlen(message)+1, MPI_CHAR, 0, myid, MPI_COMM_WORLD);
//end of your code
}
else { //myid == 0
for(source=1; source<numprocs; source++) {
//your code here
MPI_Recv(message, 100, MPI_CHAR, source, source, MPI_COMM_WORLD, &status);
//end of your code
printf("%s\n", message);
}
}
MPI_Finalize();
return 0;
}
/*
hello world!
hello world!
hello world!
*/
地址偏移量
待续