考虑以下功能:
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
的版本要慢得多?
答案 0 :(得分:11)
使用multiprocessing
需要发送有关要运行的函数的工作进程信息,而不仅仅是要传递的参数。该信息由pickling传递给主进程中的信息,将其发送到工作进程,然后在那里进行解压缩。
这导致了主要问题:
使用默认参数挑选函数很便宜;它只腌制函数的名称(加上信息让Python知道它是一个函数); worker进程只查找名称的本地副本。他们已经找到了一个命名函数f
,因此传递它几乎没有任何成就。
但挑选partial
函数涉及挑选基础函数(便宜)和所有默认参数(当默认参数为a时,昂贵 10M长[{1}})。因此,每次在list
情况下调度任务时,它都会对绑定参数进行挑选,将其发送到工作进程,工作进程进行取消,然后最终执行“真实”工作。在我的机器上,那个泡菜的大小约为50 MB,这是一个巨大的开销;在我的机器上进行快速计时测试时,对1000万长partial
list
进行酸洗和去除大约需要620毫秒(而这忽略了实际传输50 MB数据的开销)。
0
必须以这种方式腌制,因为他们不知道自己的名字;当挑选像partial
这样的函数时,f
(f
- ed)知道它的限定名称(在交互式解释器中或从程序的主模块中,它是def
),因此远程端可以通过相当于__main__.f
在本地重新创建它。但是from __main__ import f
并不知道它的名字;确定,您已将其分配给partial
,但g
和pickle
本身都不知道它是否可用限定名称partial
;它可以命名为__main__.g
或其他一百万个。因此,必须foo.fred
从头开始重新创建它所需的信息。它也是pickle
- 每次调用(不仅仅是每个工作一次),因为它不知道可调用的工作项之间的父项没有变化,并且总是试图确保它发送最新状态。
您还有其他问题(仅在pickle
情况下创建list
的时间以及调用partial
包装函数与直接调用函数的次要开销),但那些是相对于每次调用的开销腌制和取消partial
正在添加的chump更改(partial
的初始创建是将一次性开销增加到每个的一半 pickle / unpickle循环成本;通过list
调用的开销不到一微秒。)