mpi4py公司

MPI是消息传递接口(messagepassinginterface)的缩写,是并行编程的常用库。mpi4py包构建在mpi之上,允许在不同进程之间传递任意python对象。这些软件包不是默认sage安装的一部分。安装它们的时候

sage: optional_packages()

找到包名openmpi- * 和mpi4py- * 并且做

sage: install_package('openmpi-*')
sage: install_package('mpi4py-*')

请注意,openmpi编译需要一段时间(15-20分钟左右)。Openmpi可以在集群上运行,但是这需要一些设置,以便不同机器上的进程可以通信(尽管如果您在集群上,这可能已经设置好了)。最简单的情况是,如果您使用的是共享内存或多核系统,其中openmpi不需要配置就可以工作。老实说,我从未尝试在集群上运行mpi4py,尽管网上有很多关于这些主题的信息。

现在,mpi的工作方式是启动一组mpi进程,所有进程都运行相同的代码。每个进程都有一个等级,即一个标识它的数字。以下伪代码指示MPI程序的一般格式。

   ....

if my rank is n:
   do some computation ...
   send some stuff to the process of rank j
   receive some data from the process of rank k

else if my rank is n+1:
   ....

每个进程寻找它应该做什么(由其等级指定),进程可以发送数据和接收数据。让我们举个例子。在mpi_1.py文件中使用以下代码创建脚本

from mpi4py import MPI
comm = MPI.COMM_WORLD
print("hello world")
print("my rank is: %d"%comm.rank)

要运行它,您可以这样做(从sage目录中的命令行)

./local/bin/mpirun -np 5 ./sage -python mpi_1.py

命令mpirun-np5在mpi下启动程序的5个副本。在本例中,我们有5个sage副本,在纯python模式下运行脚本mpi_1.py。结果应该是5个“你好世界”加上5个不同的等级。两个最重要的mpi操作是发送和接收。考虑下面的示例,您应该将其放入脚本mpi_2.py中

from mpi4py import MPI
import numpy
comm = MPI.COMM_WORLD
rank=comm.rank
size=comm.size
v=numpy.array([rank]*5,dtype=float)
comm.send(v,dest=(rank+1)%size)
data=comm.recv(source=(rank-1)%size)
print("my rank is %d"%rank)
print("I received this:")
print(data)

上面用mpi_1.py替换为mpi_2.py的相同命令将产生5个输出,您将看到每个进程创建一个数组,然后将其传递给下一个人(最后一个人传递给第一个人)MPI.大小是mpi进程的总数。MPI.COMM公司世界就是沟通的世界。

关于MPI有一些微妙之处需要注意。小的发送被缓冲。这意味着,如果一个进程发送一个小对象,它将被openmpi存储,该进程将继续执行,它发送的对象将在目标执行接收时被接收。但是,如果对象很大,进程将挂起,直到其目标执行相应的接收。实际上,如果 [rank] * 5替换为 [rank] * 500这样做会更好

from mpi4py import MPI
import numpy
comm = MPI.COMM_WORLD
rank=comm.rank
size=comm.size
v=numpy.array([rank]*500,dtype=float)
if comm.rank==0:
   comm.send(v,dest=(rank+1)%size)
if comm.rank > 0:
    data=comm.recv(source=(rank-1)%size)
    comm.send(v,dest=(rank+1)%size)
if comm.rank==0:
    data=comm.recv(source=size-1)

print("my rank is %d"%rank)
print("I received this:")
print(data)

现在第一个进程启动一个发送,然后进程1将准备好接收,然后他将发送,进程2将等待接收,以此类推。无论我们传递的数组有多大,这都不会锁定。

一个常见的习惯用法是让一个进程,通常排名为0的进程充当领导者。该过程将数据发送给其他进程,并处理结果,并决定如何进行进一步的计算。考虑以下代码

from mpi4py import MPI
import numpy
sendbuf=[]
root=0
comm = MPI.COMM_WORLD
if comm.rank==0:
    m=numpy.random.randn(comm.size,comm.size)
    print(m)
    sendbuf=m

v=comm.scatter(sendbuf,root)

print("I got this array:")
print(v)

scatter命令获取一个列表,并将其平均分配给所有进程。在这里,根进程创建一个矩阵(它被视为一个行列表),然后将它分散到每个人(根sendbuf在进程之间平均分配)。每个进程打印它得到的行。请注意,scatter命令由每个人执行,但是当root执行它时,它充当send和receive(root从自身获得一行),而对于其他所有人来说,它只是一个receive。

有一个互补的gather命令,它将所有进程的结果收集到一个列表中。下一个示例使用分散和聚集。现在,根进程分散矩阵的行,每个进程然后平方它得到的行的元素。然后,根进程再次将这些行收集到一个新的矩阵中。

from mpi4py import MPI
import numpy
comm = MPI.COMM_WORLD
sendbuf=[]
root=0
if comm.rank==0:
    m=numpy.array(range(comm.size*comm.size),dtype=float)
    m.shape=(comm.size,comm.size)
    print(m)
    sendbuf=m

v=comm.scatter(sendbuf,root)
print("I got this array:")
print(v)
v=v*v
recvbuf=comm.gather(v,root)
if comm.rank==0:
    print(numpy.array(recvbuf))

还有一个广播命令,它向每个进程发送一个对象。考虑下面的小扩展。这和以前一样,但现在在最后,根进程向每个人发送字符串“done”,并将其打印出来。

v=MPI.COMM_WORLD.scatter(sendbuf,root)
print("I got this array:")
print(v)
v=v*v
recvbuf=MPI.COMM_WORLD.gather(v,root)
if MPI.COMM_WORLD.rank==0:
    print(numpy.array(recvbuf))

if MPI.COMM_WORLD.rank==0:
    sendbuf="done"
recvbuf=MPI.COMM_WORLD.bcast(sendbuf,root)
print(recvbuf)

MPI编程很困难。它是“精神分裂症编程”,因为你正在编写一个带有多个执行线程的程序“一个脑袋里有许多声音”。