考虑以下函数:
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
为什么使用
functools.partial
的版本这么慢? 最佳答案
使用 multiprocessing
需要向工作进程发送有关要运行的函数的信息,而不仅仅是要传递的参数。该信息由 pickling 在主进程中传输该信息,将其发送到工作进程,并在那里解压。
这导致了主要问题:
使用默认参数酸洗函数很便宜 ;它只腌制函数的名称(加上让 Python 知道它是一个函数的信息);工作进程只查找名称的本地副本。他们已经有一个命名函数 f
可以找到,因此传递它几乎不需要任何费用。
但是 酸洗 partial
函数涉及酸洗底层函数(便宜)和所有默认参数(当默认参数是 10M 长 list
时很昂贵) 091534因此,每次在 partial
情况下调度任务时,它都会酸洗绑定(bind)参数,将其发送到工作进程,工作进程解压,然后最终完成“真正”的工作。在我的机器上,pickle 的大小大约为 50 MB,这是一个巨大的开销;在我的机器上的快速计时测试中,酸洗和取消酸洗 list
的 1000 万长 0
需要大约 620 毫秒(这忽略了实际传输 50 MB 数据的开销)。partial
s必须这样腌制,因为他们不知道自己的名字;当酸洗像 f
、 f
之类的函数时(即 def
-ed)知道它的限定名称(在交互式解释器中或从程序的主模块中,它是 0x24 远程 side3413,因此可以在本地重新创建 0x231343 __main__.f
。但是 from __main__ import f
不知道它的名字;当然,您将它分配给 partial
,但是 g
和 pickle
本身都不知道它可以使用限定名称 partial
;它可以命名为 __main__.g
或一百万个其他名称。所以它必须 foo.fred
从头开始重新创建它所需的信息。它也是 pickle
每次调用(不仅仅是每个工作人员一次),因为它不知道可调用对象在工作项之间的父级中没有更改,并且它总是试图确保它发送最新状态。
您还有其他问题(仅在 pickle
情况下定时创建 list
以及调用 partial
包装函数与直接调用该函数的较小开销),但这些是相对于 the champ 更改正在添加(partial
的初始创建所增加的一次性开销比每个pickle/unpickle 周期成本的一半略低;通过partial
调用的开销不到一微秒)。
关于Python 多处理 - 为什么使用 functools.partial 比默认参数慢?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35062087/