python - 多处理池映射运行蒙特卡洛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 我将在下面的解决方案中编辑接受的答案。
解决方案
您提供的代码示例在我的 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()))
在多处理调用之前插入。
推荐阅读
- ubuntu - 如何在 Raspberry Pi 上退出 Busy Box?
- c++ - .vcxproj 文件的导入声明导致 Visual Studio 2019 中的错误
- excel - 如何一次检查多个单元格VBA
- swiftui - NavigationLink 目的地上的 SwiftUI NavigationTitle
- javascript - 更改段落中与搜索词匹配的单词的颜色
- rust - 如何将元组中的 vec 值映射到元组中的 refs vec?
- php - Swift HTTP 请求问题(不发送 HOST 标头)
- java - 如何在扩展类中使用方法并在主类中使用它们
- python - 获取(torch.cuda.FloatTensor)和权重类型(torch.FloatTensor)应该相同
- javascript - 如何以小部件的形式监听事件?