Python 中的 MPI 并行处理

21 投票
1 回答
13032 浏览
提问于 2025-04-20 22:43

我写了一个Python脚本,使用了多进程模块,这样可以让执行速度更快。这个计算是非常适合并行处理的,所以效率会随着处理器数量的增加而提高。现在,我想把这个脚本放到一个MPI程序中,MPI可以在多台计算机上管理MCMC计算。这个代码里有一个调用system()的部分,用来运行Python脚本。但是,我发现当以这种方式调用时,使用Python多进程的效率提升就消失了。

我该如何让我的Python脚本在从MPI调用时,仍然保持多进程带来的速度提升呢?

这里有一个简单的例子,虽然它比我想用的复杂代码简单,但表现出的行为是一样的。我写了一个可执行的Python脚本,叫做junk.py。

#!/usr/bin/python
import multiprocessing
import numpy as np

nproc = 3
nlen = 100000


def f(x):
    print x
    v = np.arange(nlen)
    result = 0.
    for i, y in enumerate(v):
        result += (x+v[i:]).sum()
    return result


def foo():
    pool = multiprocessing.Pool(processes=nproc)
    xlist = range(2,2+nproc)
    print xlist
    result = pool.map(f, xlist)
    print result

if __name__ == '__main__':
    foo()

当我单独从命令行运行这个脚本时,使用“top”命令可以看到三个Python进程,每个进程都占用了我这台16核机器的100% CPU。

node094:mpi[ 206 ] /usr/bin/time junk.py
[2, 3, 4]
2
3
4
[333343333400000.0, 333348333450000.0, 333353333500000.0]
62.68user 0.04system 0:21.11elapsed 297%CPU (0avgtext+0avgdata 16516maxresident)k
0inputs+0outputs (0major+11092minor)pagefaults 0swaps

但是,如果我用mpirun来调用这个脚本,每个Python进程只占用了33%的CPU,而且整体运行时间大约是之前的三倍。使用-np 2或更多的参数会产生更多的进程,但并不会加快计算速度。

node094:mpi[ 208 ] /usr/bin/time mpirun -np 1 junk.py
[2, 3, 4]
2
3
4
[333343333400000.0, 333348333450000.0, 333353333500000.0]
61.63user 0.07system 1:01.91elapsed 99%CPU (0avgtext+0avgdata 16520maxresident)k
0inputs+8outputs (0major+13715minor)pagefaults 0swaps

(补充说明:这是mpirun 1.8.1,Python 2.7.3在Linux Debian版本wheezy上。我听说在MPI程序中不总是允许使用system(),但在我这台电脑上已经工作了五年。例如,我曾经在MPI程序中通过system()调用过基于pthread的并行代码,每个线程都能达到100%的CPU使用率,这正是我想要的。此外,如果你打算建议将Python脚本串行运行并在更多节点上调用……MCMC计算涉及固定数量的链,这些链需要同步移动,所以计算不幸不能那样重新组织。)

1 个回答

20

OpenMPI的mpirun,从1.7版本开始,默认会把进程绑定到处理器核心上。也就是说,当它启动python junk.py这个进程时,会把这个进程固定在某个核心上运行。这种做法对于大多数MPI的使用场景来说是合适的。但是在这里,每个MPI任务又会通过多进程包再创建更多的进程,而这些新创建的进程会继承它们父进程的绑定状态——所以它们都被绑定到同一个核心上,互相争抢资源。(在top命令的"P"列中,你可以看到它们都在同一个处理器上)

如果你用命令mpirun -np 2,你会发现有两组各自包含三个进程的进程,每组都在不同的核心上运行,彼此之间也在争抢资源。

使用OpenMPI,你可以通过关闭绑定来避免这个问题,

mpirun -np 1 --bind-to none junk.py

或者选择其他合适的绑定方式,考虑到你运行的最终结构。MPICH也有类似的选项来处理这个问题。

需要注意的是,使用mpi创建子进程的fork()操作并不总是安全或被支持,特别是在使用了infiniband互连的集群中,但OpenMPI的mpirun/mpiexec会在不安全时给你警告。

撰写回答