首页 > 解决方案 > Ray Multiprocessing 的好处被增加的函数运行时间所抵消

问题描述

我有一个函数,我传递 6 个字典、两个字符串和一个整数。此外,传递了一个“元素”ID 列表,该函数使用一个简单的for语句循环遍历该列表。字典包含许多“元素”的结果(浮点值),其中键是“元素”标识符,值是包含 6 个浮点值的列表。字典的大小各不相同,但可以包含多达 150 万个键/值对。

该函数的第一部分询问字典,以获取与“元素”ID 有关的结果,并将它们提取到 Numpy 数组中。这些被按比例放大并进行一些简单的算术计算。请注意,这两个字符串和整数用作附加标识符和计算的起始 id。该函数返回一个字典,其中包含每个元素的计算结果。

该函数(每个元素)的平均运行时间约为 1.2 秒(初始提取为 0.2 秒,Numpy 计算为 1.0 秒。元素的数量可能多达 15,000 个,因此需要不小的运行时间。

因此,我一直在研究各种多处理模块以尝试加快运行时 ( Multiprocessing/ Dask/ joblib),但说实话,并没有太大的成功。我还没有让它们中的任何一个来减少串行运行时间。我现在专注于Ray模块,下面是调用函数的代码部分(已使用 装饰@ray.remote

mech_arg = ray.put(mech_iru_out_ult_all)
wbap_arg = ray.put(wbap_iru_out_ult_all)
thermal_arg = ray.put(thermal_out_ult_reduced)
fp_arg = ray.put(fuel_pressure_out_ult_int)
ptol_arg = ray.put(ptol_out_ult_all)
pnl_supp_arg = ray.put(panel_support_out_ult_ret)
family_arg = ray.put("IRU")
condition_arg = ray.put("ULT")
start_id_arg = ray.put(70000001)

result = [calc_crit_prin_stress.remote(mech_arg,
                                       wbap_arg,
                                       thermal_arg,
                                       fp_arg,
                                       ptol_arg,
                                       pnl_supp_arg,
                                       family_arg,
                                       condition_arg,
                                       start_id_arg,
                                       elm) for elm in reqd_elements]

results = ray.get(result)

我正在使用ray.put()为字典/字符串/整数创建共享内存对象。然后调用该函数,其中包含列表“<code>reqd_elements”中包含calc_crit_prin_stress.remote()的所有元素 id () 的循环。elm结果通过ray.get.

注意:我使用的是本地 Win10 / 32 Core / 128Gb Ram 桌面

代码执行正常,我可以看到所有内核都在任务管理器中工作,我也得到了我预期的结果,但我只看到运行时间的减少相对较小。下图显示了执行测试的实际运行时间,改变了 CPU 的数量(对于 50 个元素)。基线串行for循环运行时间为 59 秒。发现类似的时间,使用Ray4 个内核。然而,将内核进一步增加到 8/16/32,显示出收益递减,即从 4 个内核变为 32 个内核只会将运行时间减少 15 秒(约 25%)。

CPU 数量与实际运行时间(50 个元素)

然后我在函数中添加了一些简单的代码来输出函数内执行提取和 Numpy 计算所花费的时间(注意:这不会显着影响整体运行时间)。这显示了以下内容

提取时间:0.206s(x4 核)0.649s(x32 核)因子 3.15

Numpy 计算 1.129s(x4 核心)7.777s(x32 核心)因数 6.89

总计 1.335s(x4 核心)8.426s(x32 核心)因数 6.31

CPU 与函数运行时的数量

因此,尽管内核数量增加了 8 倍,但函数实际运行时间的增加几乎抵消了任何好处。
我知道在多处理的设置中涉及额外的开销,但是这些时间是在函数本身内吗?当我们在这里讨论多处理时,这是否仅限于将多个循环分配给不同的内核,或者函数本身的执行是否会分散在不同的内核上,从而导致更多的开销和增加的运行时间?我的代码中有什么东西导致了这个吗?

我会很感激这里的任何指导。谢谢

标签: python-3.xmultiprocessingray

解决方案


如果我能看到实际工作的串行和并行代码,我就能更准确。从我所看到的情况来看,您可能会通过 IPC 一次分配一个任务。因此,当您有很多进程时,它们会花费大量时间来相互瓶颈,试图从共享队列中获取信息,当然,对于许多消费者来说,它们处理这些光线的速度可能比主进程分配新任务的速度要快。

减少此问题的两种简单方法是一次发送多个任务,例如 100 或 1000 个,具体取决于您有多少以及它们倾向于计算多长时间,或者使用共享内存(不那么容易)。计算机非常擅长复制大量数据,但非常不擅长有效控制对共享数据的访问。

如果你真的希望它快一点,你可以用共享内存做一些事情,尽管这需要 python 不太适合的 bitfiddling 类型操作和序列化你想要“发送”给消费者的所有数据项。我真的不经常使用 numpy 你的数据可能已经合适了。基本上,计划是将所有带有空格的数据(或大块)存储在 Python 从 3.8 开始支持的共享内存中。然后将该表的范围分配给不同的进程,并让它们在通过队列或类似的东西完成时发出信号。您不需要将所有内容都放入共享内存中,您可以将批次移入其中,并在每个批次完成后将它们移入和移出,甚至可以将不同的区域与不同的进程相关联。它' s 非常类似于通过队列使用更大的批次,但如果传输大量数据会更快。它要复杂得多,工作量也多,所以我会先通过队列来进行批处理。

如果您使用带有生成器的多处理池,我对您当前与装饰器一起使用的技术并不十分熟悉,您可以分配批量大小,它会为您处理所有事情。


推荐阅读