考虑以下函数:

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必须这样腌制,因为他们不知道自己的名字;当酸洗像 ff 之类的函数时(即 def -ed)知道它的限定名称(在交互式解释器中或从程序的主模块中,它是 0x24 远程 side3413,因此可以在本地重新创建 0x231343 __main__.f。但是 from __main__ import f 不知道它的名字;当然,您将它分配给 partial ,但是 gpickle 本身都不知道它可以使用限定名称 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/

10-12 14:24