为什么 joblib.Parallel() 比非并行计算花费更多时间?Parallel() 不应该比非并行计算运行得更快吗?

4 python parallel-processing parallelism-amdahl

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

此代码使用列表理解来完成这项工作:

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))
Run Code Online (Sandbox Code Playgroud)

大约需要0.51s

list comprehension: 0.5140271186828613s
Run Code Online (Sandbox Code Playgroud)

此代码使用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))
Run Code Online (Sandbox Code Playgroud)

大约需要31秒

Parallel: 31.3990638256073s
Run Code Online (Sandbox Code Playgroud)

这是为什么?不应该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
Run Code Online (Sandbox Code Playgroud)

use*_*197 9

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

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

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

另外,
很容易发生比您收到的
回报更多 (在启动多重处理的生成过程上)的情况(比原始工作流程预期的加速) ......所以必须谨慎行事


最好的第一步:

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

最初的阿姆达尔的表述并没有明确说明人们必须为进入并行工作流程而支付的所谓附加 “成本”[SERIAL] ,这些成本不在原始的纯工作流程的预算中。

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

2)参数传输的附加成本- 您必须将一些数据从主流程移至新流程。它会花费附加时间,并且您必须支付此附加成本,而这在原始的纯[SERIAL]工作流程中是不存在的。

3)结果返回传输的附加成本- 您必须将一些数据从新进程移回原始(主)进程。它会花费附加时间,并且您必须支付此附加成本,而这在原始的纯[SERIAL]工作流程中是不存在的。

4)任何数据交换的附加成本(最好避免在并行工作流程中使用它 - 为什么?a)它会阻塞 + b)它很昂贵,并且您必须支付更多的附加成本才能进一步,在纯原创工作流程中您无需支付费用[SERIAL])。


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

简单地说,因为你必须支付更多的费用来启动整个精心策划的马戏团,比你从这种并行工作流组织中收到的回报要多得多(工作量太小,math.sqrt( <int> )无法证明产生 2-full 的相对巨大成本是合理的) -原始 python-(main)-session 的副本 + 所有舞蹈的编排,仅发送每个 ( <int>)-from-(main)-there 并检索返回的每个结果 ( <float>)-from-(joblib.Parallel( )-进程)-返回-(主)。

您的原始基准测试时间可以对累积成本进行充分的比较,以获得相同的结果:

[SERIAL]-<iterator> feeding a [SERIAL]-processing storing into list[]:  0.51 [s]
[SERIAL]-<iterator> feeding [PARALLEL]-processing storing into list[]: 31.39 [s]
Run Code Online (Sandbox Code Playgroud)

原始估计显示,仅仅因为忘记了必须支付的附加成本,大约有 30.9 秒的时间被“浪费”在完成相同(少量)的工作上。


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

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

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