Python 将一个大对象的方法传递给imap:通过包装该方法可将速度提高1000倍
假设Python 将一个大对象的方法传递给imap:通过包装该方法可将速度提高1000倍,python,parallel-processing,multiprocessing,Python,Parallel Processing,Multiprocessing,假设yo=yo()是一个具有方法double的大对象,该方法返回其参数乘以2 如果我将yo.double传递给multiprocessing的imap,那么速度会非常慢,因为我认为每个函数调用都会创建一个yo的副本 也就是说,这很慢: from tqdm import tqdm from multiprocessing import Pool import numpy as np class Yo: def __init__(self): self.a = np.ra
yo=yo()
是一个具有方法double
的大对象,该方法返回其参数乘以2
如果我将yo.double
传递给multiprocessing
的imap
,那么速度会非常慢,因为我认为每个函数调用都会创建一个yo
的副本
也就是说,这很慢:
from tqdm import tqdm
from multiprocessing import Pool
import numpy as np
class Yo:
def __init__(self):
self.a = np.random.random((10000000, 10))
def double(self, x):
return 2 * x
yo = Yo()
with Pool(4) as p:
for _ in tqdm(p.imap(yo.double, np.arange(1000))):
pass
输出:
0it [00:00, ?it/s]
1it [00:06, 6.54s/it]
2it [00:11, 6.17s/it]
3it [00:16, 5.60s/it]
4it [00:20, 5.13s/it]
0it [00:00, ?it/s]
1000it [00:00, 14919.34it/s]
但是,如果我使用函数double\u wrap
包装yo.double
,并将其传递给imap
,那么它基本上是瞬时的
def double_wrap(x):
return yo.double(x)
with Pool(4) as p:
for _ in tqdm(p.imap(double_wrap, np.arange(1000))):
pass
输出:
0it [00:00, ?it/s]
1it [00:06, 6.54s/it]
2it [00:11, 6.17s/it]
3it [00:16, 5.60s/it]
4it [00:20, 5.13s/it]
0it [00:00, ?it/s]
1000it [00:00, 14919.34it/s]
包装函数如何以及为什么会改变行为
我使用的是Python 3.6.6。关于复制,您是对的
yo.double
是一种“绑定方法”,绑定到您的大对象。当您将它传递到pool方法中时,它将使用它对整个实例进行pickle,将其发送到子进程并在那里取消pickle。子进程处理的iterable的每个块都会发生这种情况。pool.imap
中的chunksize
的默认值为1,因此您在iterable中处理的每个项目都会遇到此通信开销
相反,当您传递double_wrap
时,您只是传递一个模块级函数。实际上只有它的名称会被pickle,子进程会从\uuuuu main\uuuuu
导入函数。由于您的操作系统显然支持分叉,因此您的double\u wrap
函数将可以访问yo
的分叉yo
实例。在这种情况下,您的大对象不会被序列化(pickle),因此与其他方法相比,通信开销很小
@Darkonaut我只是不明白为什么使功能模块级别阻止复制对象。毕竟,函数需要有一个指向yo对象本身的指针——这需要所有进程复制yo,因为它们不能共享内存 在子进程中运行的函数将自动查找对全局
yo
的引用,因为您的操作系统(OS)正在使用fork创建子进程。分叉会导致整个父进程的克隆,只要父进程和子进程都不改变特定对象,它们都会在同一内存位置看到同一对象
仅当父对象或子对象更改了对象上的某些内容时,才会在子进程中复制该对象。这被称为“写时复制”,发生在操作系统级别,而在Python中没有注意到这一点。您的代码在Windows上无法工作,Windows使用“spawn”作为新进程的启动方法
现在我把上面写的“对象被复制”简化了一点,因为操作系统操作的单元是一个“页面”(通常是4KB大小)。这个答案将是一个很好的后续阅读,有助于扩大您的理解。我的答案有点不清楚?@Darkonaut我只是不明白为什么设置功能模块级别会阻止复制对象。毕竟,函数需要有一个指向
yo
对象本身的指针–这应该要求所有进程复制yo
,因为它们不能共享内存。我将对上述注释的响应重新定位到我的答案中。