为什么Dask的执行速度如此之慢,而多处理的执行速度却如此之快?

时间:2019-09-06 10:59:29

标签: python parallel-processing dask parallelism-amdahl

为了更好地了解并行,我正在比较一组不同的代码。

这是基本的代码(code_piece_1)。

for循环

import time

# setup
problem_size = 1e7
items = range(9)

# serial
def counter(num=0):
    junk = 0
    for i in range(int(problem_size)):
        junk += 1
        junk -= 1
    return num

def sum_list(args):
    print("sum_list fn:", args)
    return sum(args)

start = time.time()
summed = sum_list([counter(i) for i in items])
print(summed)
print('for loop {}s'.format(time.time() - start))

这段代码以串行方式(for循环)运行了一个耗时者,并得到了结果

sum_list fn: [0, 1, 2, 3, 4, 5, 6, 7, 8]
36
for loop 8.7735116481781s

多处理

可以将多处理方式视为实现并行计算的一种方式吗?

我假设是,因为doc这样说。

这是code_piece_2

import multiprocessing
start = time.time()
pool = multiprocessing.Pool(len(items))
num_to_sum = pool.map(counter, items)
print(sum_list(num_to_sum))
print('pool.map {}s'.format(time.time() - start))

此代码以多处理方式同时运行消费者,并获得了结果

sum_list fn: [0, 1, 2, 3, 4, 5, 6, 7, 8]
36
pool.map 1.6011056900024414s

很明显,在这种情况下,多处理程序比串行处理要快。

黄昏

Dask是用于Python中并行计算的灵活库。

此代码(code_piece_3)与Dask的消费者同时运行(我不确定我是否以正确的方式使用Dask。)

@delayed
def counter(num=0):
    junk = 0
    for i in range(int(problem_size)):
        junk += 1
        junk -= 1
    return num
@delayed
def sum_list(args):
    print("sum_list fn:", args)
    return sum(args)

start = time.time()
summed = sum_list([counter(i) for i in items])
print(summed.compute())
print('dask delayed {}s'.format(time.time() - start))

我知道了

sum_list fn: [0, 1, 2, 3, 4, 5, 6, 7, 8]
36
dask delayed 10.288054704666138s

我的CPU有6个物理核心

问题

为什么Dask的执行速度如此之慢,而多处理的执行速度却如此之快?

我使用Dask的方式错误吗?如果是,正确的方法是什么?

注意:请讨论此特定案例或其他特定具体案例。请不要一般地说。

3 个答案:

答案 0 :(得分:1)

  

Q 为什么并行计算要比串行计算花费更长的时间?

因为有更多的方法可以加载到CPU上执行(即使在指令/预期计算块的第一步首先进入CPU之前,“很多”仍然“令人敬畏”),然后纯[SERIAL]的情况,在执行流程中未添加任何附加成本。

对于这些(隐藏在源代码中)的附加操作(您在[TIME]域(此类“准备工作”的持续时间)和[SPACE]域(分配更多RAM包含操作[PARALLEL]的代码所需的所有涉及结构(好吧,如果我们的术语学得很准确的话,大多数情况下仍然是[CONCURRENT]操作的代码),这又使您花费{ {1}},因为每个RAM-I / O花费的费用大约是[TIME]的1/3)

结果?

除非,您的工作负载包具有“足够”的工作量,可以并行执行(非阻塞,无锁,互斥,共享,无依赖项,无我/ O,...实际上是独立的,具有最少的RAM-I / O重新获取),很容易“付款方式比您获得的回报多”

有关附加费用的详细信息以及对最终加速产生巨大影响的事情,请开始阅读the criticism of blind using the original, overhead naive formulation of the Amdahl's law here

答案 1 :(得分:1)

您拥有的代码需要GIL,因此一次仅运行一个任务,而您得到的只是额外的开销。例如,如果您将分布式调度程序与进程一起使用,那么您将获得更好的性能。

答案 2 :(得分:1)

在您的示例中,dask比python多处理要慢,因为您没有指定调度程序,所以dask使用多线程后端,这是默认设置。正如mdurant所指出的那样,您的代码不会释放GIL,因此多线程无法并行执行任务图。

在这里查看有关该主题的完整概述:https://docs.dask.org/en/stable/scheduler-overview.html

对于您的代码,可以通过调用以下命令切换到多处理后端:  .compute(scheduler='processes')

如果使用多处理后端,则进程之间的所有通信仍需要通过主进程。因此,您可能还需要检出分布式调度程序,在该调度程序中工作进程可以直接相互通信,这对于复杂的任务图尤其有用。此外,分布式调度程序支持工作窃取以平衡流程之间的工作,并具有Web界面,可提供有关正在运行的任务的某些诊断信息。即使只想在本地计算机上进行计算,使用分布式调度程序而不是多处理调度程序通常也很有意义。