Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/305.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 将一个大对象的方法传递给imap:通过包装该方法可将速度提高1000倍_Python_Parallel Processing_Multiprocessing - Fatal编程技术网

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
,因为它们不能共享内存。我将对上述注释的响应重新定位到我的答案中。