Python多处理 - 为什么使用functools.partial比默认参数慢?

时间:2016-01-28 12:54:07

标签: python python-3.x python-multiprocessing functools

考虑以下功能:

NSButtonCell

如果我使用def f(x, dummy=list(range(10000000))): return x ,我会得到以下时间:

multiprocessing.Pool.imap

现在,如果我使用import time import os from multiprocessing import Pool def f(x, dummy=list(range(10000000))): return x start = time.time() pool = Pool(2) for x in pool.imap(f, range(10)): print("parent process, x=%s, elapsed=%s" % (x, int(time.time() - start))) parent process, x=0, elapsed=0 parent process, x=1, elapsed=0 parent process, x=2, elapsed=0 parent process, x=3, elapsed=0 parent process, x=4, elapsed=0 parent process, x=5, elapsed=0 parent process, x=6, elapsed=0 parent process, x=7, elapsed=0 parent process, x=8, elapsed=0 parent process, x=9, elapsed=0 而不是使用默认值:

functools.partial

为什么使用import time import os from multiprocessing import Pool from functools import partial def f(x, dummy): return x start = time.time() g = partial(f, dummy=list(range(10000000))) pool = Pool(2) for x in pool.imap(g, range(10)): print("parent process, x=%s, elapsed=%s" % (x, int(time.time() - start))) parent process, x=0, elapsed=1 parent process, x=1, elapsed=2 parent process, x=2, elapsed=5 parent process, x=3, elapsed=7 parent process, x=4, elapsed=8 parent process, x=5, elapsed=9 parent process, x=6, elapsed=10 parent process, x=7, elapsed=10 parent process, x=8, elapsed=11 parent process, x=9, elapsed=11 的版本要慢得多?

1 个答案:

答案 0 :(得分:11)

使用multiprocessing需要发送有关要运行的函数的工作进程信息,而不仅仅是要传递的参数。该信息由pickling传递给主进程中的信息,将其发送到工作进程,然后在那里进行解压缩。

这导致了主要问题:

使用默认参数挑选函数很便宜;它只腌制函数的名称(加上信息让Python知道它是一个函数); worker进程只查找名称的本地副本。他们已经找到了一个命名函数f,因此传递它几乎没有任何成就。

挑选partial函数涉及挑选基础函数(便宜)和所有默认参数(当默认参数为a时,昂贵 10M长[{1}})。因此,每次在list情况下调度任务时,它都会对绑定参数进行挑选,将其发送到工作进程,工作进程进行取消,然后最终执行“真实”工作。在我的机器上,那个泡菜的大小约为50 MB,这是一个巨大的开销;在我的机器上进行快速计时测试时,对1000万长partial list进行酸洗和去除大约需要620毫秒(而这忽略了实际传输50 MB数据的开销)。

0必须以这种方式腌制,因为他们不知道自己的名字;当挑选像partial这样的函数时,ff - ed)知道它的限定名称(在交互式解释器中或从程序的主模块中,它是def ),因此远程端可以通过相当于__main__.f在本地重新创建它。但是from __main__ import f并不知道它的名字;确定,您已将其分配给partial,但gpickle本身都不知道它是否可用限定名称partial;它可以命名为__main__.g或其他一百万个。因此,必须foo.fred从头开始重新创建它所需的信息。它也是pickle - 每次调用(不仅仅是每个工作一次),因为它不知道可调用的工作项之间的父项没有变化,并且总是试图确保它发送最新状态。

您还有其他问题(仅在pickle情况下创建list的时间以及调用partial包装函数与直接调用函数的次要开销),但那些是相对于每次调用的开销腌制和取消partial正在添加的chump更改(partial的初始创建是将一次性开销增加到每个的一半 pickle / unpickle循环成本;通过list调用的开销不到一微秒。)