Python MPI Message Passing
分布式内存 MIMD 系统最常用的编程方法是 message passing (消息传递),或消息传递的某些变体。 MPI 是使用最广泛的标准。
在基本消息传递中,进程通过显式发送和接收消息来协调它们的活动。 显式发送和接收消息称为点对点通信。
MPI 的发送和接收调用以下列方式运行:
- 首先,进程 A 决定要向进程 B 发送消息。
- 然后,进程 A 将其所有必要数据打包到进程 B 的缓冲区中。
- 进程A表示应该通过调用Send函数将数据发送给进程B。
- 在进程 B 可以接收数据之前,它需要确认它想要接收数据。 进程 B 通过调用 Recv 函数来完成此操作。
这样,进程每次发送消息,必然有一个进程也表示要接收消息。即对 Send 和 Recv 的调用总是成对的。
进程如何知道将消息发送到哪里?
当一个 MPI 程序第一次启动时,进程的数量是固定的(有一种方法可以创建更多的进程,但我们暂时忽略它。)每个进程被分配一个从 0 开始的唯一整数。这个整数被称为 进程的等级以及在发送和接收消息时如何识别每个进程。
MPI 进程被安排在逻辑集合中,这些逻辑集合定义允许哪些进程发送和接收消息。 这种类型的集合称为 communicator (通信器)。 通信器可以按层级排列,但在 MPI 中很少使用,这里不再赘述。 MPI 程序启动时存在一个特殊的通信器,它包含了 MPI 程序中的所有进程。 这个通讯器叫做 MPI.COMM_WORLD
。 在 mpi4py 中,通信器由 Comm 类表示。
为了让进程了解其他进程,MPI 在通信器上提供了两种方法。
其中第一个称为 Get_size()
,它返回通信器中包含的进程总数(通信器的大小)。
其中第二个称为 Get_rank()
,它返回调用进程在通信器中的排名。 请注意,Get_rank() 将为 MPI 程序中的每个进程返回不同的值。
术语
为简单起见,我们将“排名为 N 的进程”和“进程 N”称为同一进程。
以下代码获取 MPI.COMM_WORLD
通信器的大小,以及通信器内进程的等级。 我们将创建一个名为 mpi1.py
的文件并运行此代码以查看每个进程的 size 和 rank 值。
1 | from mpi4py import MPI |
现在使用命令运行程序:
1 | mpiexec -n 4 python mpi1.py |
输出
1 | size=4, rank=1 |
您注意到程序打印值的顺序是什么?提示:尝试运行该程序几次,看看会发生什么。
mpiexec
当一个 MPI 程序运行时,每个进程都包含相同的代码。 然而,正如我们所见,只有一个区别:每个进程都被分配了不同的排名值。 这允许将每个进程的代码嵌入到一个程序文件中。
在下面的代码中,所有进程都以相同的两个数字 a 和 b 开头。 然而,虽然只有一个文件,但每个进程对数字执行不同的计算。 进程 0 打印数字的总和,进程 1 打印数字相乘的结果,进程 2 打印最大值。
创建一个名为 mpi2.py
的程序,其中包含以下代码。
1 | from mpi4py import MPI |
使用以下命令运行此程序:
1 | mpiexec -n 3 python mpi2.py |
输出
1 | 9.0 |
Point-to-point communication
如前所述,最简单的消息传递涉及两个进程:发送方和接收方。 让我们首先演示为两个进程设计的程序。 一个将抽取一个随机数,然后将其发送给另一个。 我们将使用例程 Send 和 Recv 来完成此操作。
创建一个名为 mpi3.py 的程序,其中包含以下代码。
1 | import numpy |
(未完)