在单机条件下,MPI4PY与纯Python多进程代码来比较是否有性能优势???

如题:

最近在看MPI4PY的代码,但是发现这东西除了编写简洁外(少量代码实现复杂的多进程通信,包括单机和多机),好像也没有别的太多功能,当然MPI本身在多机通信广播、规约上做的很成熟,但是假设我们只是在一个单机上来运行Python多进程代码,那么使用MPI4PY除了代码简洁上以外在运行性能上是否会有区别呢???

本文探讨的主题就是如果我不使用分布式运行代码,而只是使用单机运行多进程代码,那么除了编码简洁外,MPI编程是否会有性能上的优势。

本文采用几个代码来探讨这个问题:本文代码为多子进程操作父进程中的一段内存,每次操作都将该内存中数据自加一。

给出第一个代码,纯Python实现,不使用MPI的情况下在单机运行多进程:

import ctypes
import time
import multiprocessing
import numpy as np

#NUM_PROCESS = multiprocessing.cpu_count()
NUM_PROCESS = 4

size = 1000000


def worker(index):
    main_nparray = np.frombuffer(shared_array_base[index], dtype=ctypes.c_double)
    for i in range(10000):
        main_nparray[:] = index + i
    return index


if __name__ == "__main__":
    shared_array_base = []
    for _ in range(NUM_PROCESS):
        shared_array_base.append(multiprocessing.Array("d", size, lock=False))

    pool = multiprocessing.Pool(processes=NUM_PROCESS)

    a = time.time()
    result = pool.map(worker, range(NUM_PROCESS))
    b = time.time()
    print(b-a)
    #print(result)


    for i in range(NUM_PROCESS):
        main_nparray = np.frombuffer(shared_array_base[i], dtype=ctypes.c_double)
        print(main_nparray)
        print(type(main_nparray))
        print(main_nparray.shape)

第二个代码,使用MPI4PY,多非root进程每一次操作都进行同步,root进程与非root进程通信采用分发和收集操作。

from mpi4py import MPI
import numpy as np
import time


comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()


recv_data = np.zeros(1000000, dtype=np.double)


send_data = None
if rank == 0:
    send_data = np.zeros((size, 1000000), dtype=np.double)
    send_data[1]+=1
    send_data[2]+=2
    send_data[3]+=3


if rank ==0:
    a=time.time()
for i in range(10000):

    comm.Scatter(send_data, recv_data, root=0)

    #if rank != 0:
    #    recv_data += 1
    recv_data += 1

    comm.Gather(recv_data, send_data, root=0)
if rank ==0:
    b=time.time()
    print(send_data)
    print(b-a)

第三个代码,同样使用MPI4PY,多非root进程每一次操作都不进行同步,root进程与非root进程通信采用点对点方式,并且root进程与非root进程同样使用异步操作。

from mpi4py import MPI
import numpy as np
import time


comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()


recv_data = np.zeros(1000000, dtype=np.double)


send_data = None
if rank == 0:
    send_data = np.zeros((size-1, 1000000), dtype=np.double)
    send_data[0]+=0
    send_data[1]+=1
    send_data[2]+=2
    send_data[3]+=3




if rank == 0:
    a, b, c, d = 10000, 10000, 10000, 10000
    a_one_finish = True
    b_one_finish = True
    c_one_finish = True
    d_one_finish = True

    a_time = time.time()
    while True:
        if a != 0:
            if a_one_finish == True:
                a_one_finish == False
                a_req = comm.Isend(send_data[0], dest=1)
                a_req2 = comm.Irecv(send_data[0], source=1)
                a_one_finish = a_req2.test()[0]
                if a_one_finish==True:
                    a -= 1
            else:
                a_one_finish = a_req2.test()[0]
                if a_one_finish==True:
                    a -= 1

        if b != 0:
            if b_one_finish == True:
                b_one_finish == False
                b_req = comm.Isend(send_data[1], dest=2)
                b_req2 = comm.Irecv(send_data[1], source=2)
                b_one_finish = b_req2.test()[0]
                if b_one_finish==True:
                    b -= 1
            else:
                b_one_finish = b_req2.test()[0]
                if b_one_finish==True:
                    b -= 1


        if c != 0:
            if c_one_finish == True:
                c_one_finish == False
                c_req = comm.Isend(send_data[2], dest=3)
                c_req2 = comm.Irecv(send_data[2], source=3)
                c_one_finish = c_req2.test()[0]
                if c_one_finish==True:
                    c -= 1
            else:
                c_one_finish = c_req2.test()[0]
                if c_one_finish==True:
                    c -= 1


        if d != 0:
            if d_one_finish == True:
                d_one_finish == False
                d_req = comm.Isend(send_data[3], dest=4)
                d_req2 = comm.Irecv(send_data[3], source=4)
                d_one_finish = d_req2.test()[0]
                if d_one_finish==True:
                    d -= 1
            else:
                d_one_finish = d_req2.test()[0]
                if d_one_finish==True:
                    d -= 1


        if a+b+c+d ==0:
            break



    b_time = time.time()
    print(b_time-a_time)
    print(send_data)


if rank != 0:
    for _ in range(10000):
        comm.Recv(recv_data, source=0)
        recv_data += 1
        comm.Send(recv_data, dest=0)
        #print(recv_data)

注意:由于采用了点对点异步通信的方式,因此代码3中将总共运行的进程数些死了,只能运行5个进程,其中非root进程为4个。

===================================================

测试平台1:Xeon CPU: 24物理核心,48逻辑核心

代码1:运行时间:4.73 秒

代码2:运行时间:144.68 秒

代码3:运行时间:140.43 秒

测试平台2:i7 8代台式机CPU: 6物理核心,12逻辑核心

代码1:运行时间:16.05 秒

代码2:运行时间:80.61 秒

代码3:运行时间:84.24 秒

特别说明:

从上面的结果我们可以看到代码2的性能会比代码3的性能好一些,不过考虑到运行的误差性,我们可以把代码2与代码3的运行时间看做相当(几乎相同)。

其中,

代码2的运行命令为:mpirun -np 4   python x2.py

代码3的运行命令为:mpirun -np 5   python x3.py

在平台1上之所以代码1性能最好主要是因为在消息传递不是主要影响性能的因素时计算性能主要受向量计算能力所影响,而平台1的向量计算能力要优于平台2,因此平台1上代码1的性能最好。

平台1上之所以代码2,代码3的性能最差是因为当消息传统为性能主要影响因素时平台1的内存数据的传递速度要慢于平台2。

补充:共享内存的方式时多进程间消息传递几乎是不损耗时间的,几乎如在自身进程的内存空间中操作一样。 

===================================================

从上面的计算结果可以推断出即使在单机情况下MPI编码的多进程通信也并不是采用共享内存的方式,而是进程间内存拷贝的方式,所以我们可以知道在单机情况下MPI编程的代码在消息传递方面并不占优势,只能说性能适合,其性能还是比不上手动编写的共享内存通信方式的性能。

呼应开篇之说,

本文探讨的主题就是如果我不使用分布式运行代码,而只是使用单机运行多进程代码,那么除了编码简洁外,MPI编程是否会有性能上的优势。

答案就是:

只使用单机运行多进程代码,那么除了编码简洁外(快速编程外),MPI编程并不会有性能上的优势。

总结一下:

MPI的优势:

使用MPI可以便捷的编写多进程代码,编写较为容易的将单进程代码改写为多进程代码。

或者说,MPI的优势是可以快速的在多机的分布式平台环境下运行,具有较好的扩充性,比较典型的应用场景就是在超算中心的超算平台上计算流体动力学这样的数值计算任务。比较MPI的代码可以很好的在上百台服务构成的计算环境中运行,而这种计算场景我们如果不使用MPI编程而是手动去编写代码去实现消息通信,那不仅工作量巨大,而且性能难以保证,甚至可能是一个不太可能实现的工作。

因此,如果你打算要你的代码(数值计算任务)以后在多机环境下(众多服务器环境,不太指那种两台,三台服务器环境)分布式运行那么MPI是你很好的选择,或者说是你唯一的选择,但是如果你就一台服务器,而且未来也不太可能有大集群服务器运行的可能,那么mpi编程并没有什么优势,或者说至少不是第一选择,也不是第二选择,或者说你不会别的并行通信方式的编码而只会mpi那么你选mpi编程完全OK。

参考:

https://materials.jeremybejarano.com/MPIwithPython/#

本博客是博主个人学习时的一些记录,不保证是为原创,个别文章加入了转载的源地址还有个别文章是汇总网上多份资料所成,在这之中也必有疏漏未加标注者,如有侵权请与博主联系。
原文地址:https://www.cnblogs.com/devilmaycry812839668/p/15119432.html