首页 > 解决方案 > 多处理池映射运行蒙特卡洛python模拟的速度非常慢

问题描述

我有一个 python 代码,它为一组参数运行 2D 扩散模拟。我需要多次运行代码,O(1000),就像蒙特卡洛方法一样,每次使用不同的参数设置。为了更快地做到这一点,我想使用我机器(或集群)上的所有内核,以便每个内核运行一个代码实例。

过去,我通过编写 python 包装器成功地为串行fortran 代码完成了此操作,然后使用多处理映射(或多参数情况下的星图)在模拟集合中调用 fortan 代码。它工作得非常好,因为您循环了 1000 次模拟,并且 python 包装器在完成之前的集成后,一旦它变得免费,就会将新的集成移植到核心。

但是,现在当我将其设置为运行我的 python(而不是 fortran)代码的多个实例时,我发现它非常慢,比简单地在单个内核上以串行方式运行代码 1000 次要慢得多。使用系统监视器,我看到一次只有一个内核在工作,而且它的负载永远不会超过 10-20%,而我当然希望看到 N 个内核在接近 100% 的情况下运行(就像我外包 fortran 作业时的情况一样)。

我认为这可能是一个写入问题,所以我仔细检查了代码以确保所有绘图都已关闭,实际上根本没有文件/磁盘访问,我现在只有一个打印语句在最后打印出最终诊断。

我的代码结构是这样的

我在toy_diffusion_2d.py中有主要的 python 代码,它有一个字典的单个 arg,其中包含运行参数:

def main(arg)
loop over timesteps:
    calculation simulation over a large-grid
print the result statistic

然后我编写了一个“包装器”脚本,在其中导入主要的模拟代码并尝试并行运行它:

from multiprocessing import Pool,cpu_count
import toy_diffusion_2d

# dummy list of arguments
par1=[1,2,3]
par2=[4,5,6]

# make a list of dictionaries to loop over, 3x3=9 simulations in all.
arglist=[{"par1":p1,"par2":p2} for p1 in par1 for p2 in par2] 

ncore=min(len(arglist),int(cpu_count()))
with Pool(processes=ncore) as p:
    p.map(toy_diffusion_2d.main,arglist)

上面是一个较短的释义示例,我的实际代码较长,所以我将它们放在这里:

主要代码:http ://clima-dods.ictp.it/Users/tompkins/files/toy_diffusion_2d.py

您可以使用默认值运行它,如下所示:

python3 toy_diffusion_2d.py

包装脚本:http ://clima-dods.ictp.it/Users/tompkins/files/toy_diffusion_loop.py

您可以像这样运行 4 成员合奏:

python3 toy_diffusion_loop.py --diffK=[33000,37500] --tau_sub=[20,30]

(请注意,每次运行的最终统计数据都略有不同,即使模型的值与随机模型相同,这是随机艾伦卡恩方程的一个版本,以防有人感兴趣,但在扩散项上使用愚蠢的显式求解器) .

正如我所说,第二个并行代码有效,但正如我所说,它非常慢......就像它一直在门控一样。

我也尝试过使用starmap,但这并没有什么不同,这几乎就像桌面一次只允许一个python解释器运行......?我花了几个小时在上面,我几乎要在 Fortran 中重写代码了。我确定我只是在做一些非常愚蠢的事情来阻止并行执行。

编辑(1):这个问题发生在 4.15.0-112-generic x86_64 GNU/Linux 上,使用 Python 3.6.9

作为对评论的回应,事实上我也发现它在我的 MAC 笔记本电脑上运行良好......

编辑(2):所以看来我的问题与其他几个帖子有点重复,抱歉!除了 Pavel 提供的有用链接外,我还发现此页面非常有用:Importing scipy Breaks multiprocessing support in Python 我将在下面的解决方案中编辑接受的答案。

标签: pythonperformancemultiprocessingpython-multiprocessingmontecarlo

解决方案


您提供的代码示例在我的 MacOS Catalina 10.15.6 上运行良好。我猜你正在使用一些 Linux 发行版,根据这个答案,由于与 OpenBLAS 库链接,numpy import 可能会干扰核心亲和力。

如果你的 Unix 支持调度器接口,这样的事情起作用:

>>> import os
>>> os.sched_setaffinity(0, set(range(cpu_count)))

另一个可以很好地解释这个问题的问题在这里找到,建议的解决方案是:

os.system('taskset -cp 0-%d %s' % (ncore, os.getpid()))

在多处理调用之前插入。


推荐阅读