为什么乔布利布。Parallel() 比非并行计算花费更多的时间?Parallel() 不应该比非并行计算运行得更快吗?



joblib模块提供了一个简单的帮助程序类,用于使用多处理编写并行循环。

此代码使用列表推导来完成这项工作:

import time
from math import sqrt
from joblib import Parallel, delayed
start_t = time.time()
list_comprehension = [sqrt(i ** 2) for i in range(1000000)]
print('list comprehension: {}s'.format(time.time() - start_t))

大约需要 0.51s

list comprehension: 0.5140271186828613s

此代码使用joblib.Parallel()构造函数:

start_t = time.time()
list_from_parallel = Parallel(n_jobs=2)(delayed(sqrt)(i ** 2) for i in range(1000000))
print('Parallel: {}s'.format(time.time() - start_t))

大约需要 31 秒

Parallel: 31.3990638256073s

为什么?Parallel()不应该变得比非并行计算更快吗?

这是cpuinfo的一部分:

processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 79
model name      : Intel(R) Xeon(R) CPU @ 2.20GHz
stepping        : 0
microcode       : 0x1
cpu MHz         : 2200.000
cache size      : 56320 KB
physical id     : 0
siblings        : 8
core id         : 0
cpu cores       : 4
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes

Parallel()不应该变得比非并行计算更快吗?

嗯,这取决于,很大程度上取决于情况(无论是joblib.Parallel()还是其他方式)。

没有任何好处是免费提供的 (自 1917 年以来,所有这些承诺都未能兑现......

另外,很容易支付比您

收到的更多的费用(在启动多处理的生成过程中)
比您收到的(在原始工作流程中预期的加速)......所以必须给予应有的照顾


最好的第一步:

重新审视阿姆达尔定律的修订和对流程调度效应的批评(通过流程流的重组和至少在某些部分使用并行流程调度来实现加速)。

最初的阿姆达尔公式没有明确说明进入并行工作流程必须支付的所谓附加"成本">,这些成本不在原始的纯工作流程的预算中[SERIAL]

1) 进程实例化在 python 中总是很昂贵,因为它首先必须复制尽可能多的副本(O/S 驱动的 RAM 分配大小为n_jobs(2)-拷贝 + O/S 驱动的复制主 python 会话的 RAM 映像)(基于线程的多处理会做负加速,因为在所有生成的线程中仍然存在 GIL-lock 重新[SERIAL]工作步骤,所以你什么也得不到, 虽然您为每个附加的 GIL-ackquire/GIL-release 步舞步支付了巨大的生成 + 附加成本 - 计算密集型任务的可怕反模式,但它可能有助于掩盖一些与 I/O 相关的延迟情况,但绝对不是计算密集型工作负载的情况)

2) 参数传输的附加成本 - 您必须将一些数据从主进程移动到新进程。它需要额外的时间,你必须支付这个附加费用,这在原始的、纯[SERIAL]的工作流程中是不存在的。

3)结果返回传输的附加成本 - 您必须将一些数据从新数据移回原始(主)流程。它需要额外的时间,你必须支付这个附加费用,这在原始的、纯[SERIAL]的工作流程中是不存在的。

4) 任何数据交换的附加成本(最好避免在并行工作流中使用它 - 为什么?a) 它阻止 +b)它很昂贵,您必须支付更多的附加成本才能走得更远,而您不会在纯[SERIAL]原始工作流程中支付)。


为什么joblib.Parallel()比非并行计算花费更多的时间?

简单地说,因为你必须付出更多的代价来启动整个编排的马戏团,而不是你从这种并行工作流组织获得的回报(math.sqrt( <int> )工作量太少,无法证明产生原始 python-(main)-session 的 2 个完整副本 + 所有舞蹈编排的相对巨大的成本来发送每个(<int>)-from-(main)-那里并检索返回的每个结果 (<float>)-from-(joblib。Parallel()-process)-back-to-(main)。

原始基准测试时间提供了足够的累积成本比较,以达到相同的结果:

[SERIAL]-<iterator> feeding a [SERIAL]-processing storing into list[]:  0.51 [s]
[SERIAL]-<iterator> feeding [PARALLEL]-processing storing into list[]: 31.39 [s]

原始估计说,大约 30.9 秒被">浪费">在做同样(少量)的工作上,只是忘记了人们必须支付的附加成本。


那么,如何衡量您必须支付多少费用...在必须付款之前...

基准测试,基准测试,基准测试实际代码...(原型)

如果有兴趣对这些成本进行基准测试——[us]需要多长时间(即在任何有用的工作开始之前你必须支付多少费用)才能完成 1)、2) 或 3),在能够决定什么是最低工作包之前,在自己的平台上发布基准测试模板来测试和验证这些主要成本, 这可以证明这些不可避免的费用是合理的,并且与纯[SERIAL]原始相比,产生更大(最好更大)>> 1.0000"积极"的加速。

最新更新