Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/325.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 使用multiprocessing.Manager.list而不是实际列表会使计算耗时_Python_Multiprocessing - Fatal编程技术网

Python 使用multiprocessing.Manager.list而不是实际列表会使计算耗时

Python 使用multiprocessing.Manager.list而不是实际列表会使计算耗时,python,multiprocessing,Python,Multiprocessing,我想尝试使用多处理的不同方法,从以下示例开始: $cat multi\u bad.py 将多处理作为mp导入 从时间上导入睡眠 从随机导入randint def f(l,t): #睡眠(30) 返回和(对于l中的x,x小于t) 如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu': l=[randint(11000)表示范围内(25000)] t=[randint(11000)表示范围(4)] #睡眠(15) pool=mp.pool(进程=4) 结果=pool.

我想尝试使用
多处理
的不同方法,从以下示例开始:

$cat multi\u bad.py
将多处理作为mp导入
从时间上导入睡眠
从随机导入randint
def f(l,t):
#睡眠(30)
返回和(对于l中的x,x小于t)
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
l=[randint(11000)表示范围内(25000)]
t=[randint(11000)表示范围(4)]
#睡眠(15)
pool=mp.pool(进程=4)
结果=pool.starmap_async(f,[(l,x)表示t中的x])
打印(result.get())
这里,
l
是一个列表,当生成4个进程时,它会被复制4次。为了避免这种情况,文档页面提供了使用队列、共享数组或使用
multiprocessing.Manager
创建的代理对象。对于最后一个,我更改了
l
的定义:

$ diff multi_bad.py multi_good.py 
10c10,11
<     l = [randint(1, 1000) for _ in range(25000)]
---
>     man = mp.Manager()
>     l = man.list([randint(1, 1000) for _ in range(25000)])
文档确实说这种方式比共享阵列慢,但这感觉不对。我也不确定如何通过分析这一点来获得更多的信息。我错过什么了吗

另外,对于共享阵列,我的时间低于0.25秒


p.p.S.这是在Linux和Python 3.3上实现的。

这是意料之中的,因为访问共享对象意味着必须对请求进行pickle处理,并通过某种信号发送请求/syscall取消pickle处理请求,以相同的方式执行并返回结果

基本上,你应该尽量避免共享内存。这会产生更多可调试的代码(因为并发性要少得多),并且速度会更快

只有在真正需要时才应使用共享内存(例如,共享千兆字节的数据,以便复制数据时需要太多RAM,或者如果进程能够通过该共享内存进行交互)

另一方面,使用管理器可能比使用共享数组慢得多,因为管理器必须能够处理任何PyObject*,因此必须进行pickle/unpickle等操作,而数组可以避免很多这种开销

从多重处理的文档中:

管理者提供了一种创建数据的方法,这些数据可以在 不同的过程。管理器对象控制服务器进程,该进程 管理共享对象。其他进程可以访问共享对象 通过使用代理

因此,使用管理器意味着产生一个新的进程,该进程只用于处理共享内存,这可能就是它需要更多时间的原因

如果您尝试分析代理的速度,它将比非共享列表慢得多:

>>> import timeit
>>> import multiprocessing as mp
>>> man = mp.Manager()
>>> L = man.list(range(25000))
>>> timeit.timeit('L[0]', 'from __main__ import L')
50.490395069122314
>>> L = list(range(25000))
>>> timeit.timeit('L[0]', 'from __main__ import L')
0.03588080406188965
>>> 50.490395069122314 / _
1407.1701119638526
虽然
数组
的速度并不是很慢:

>>> L = mp.Array('i', range(25000))
>>> timeit.timeit('L[0]', 'from __main__ import L')
0.6133401393890381
>>> 0.6133401393890381 / 0.03588080406188965
17.09382371507359
由于最基本的操作速度很慢,并且不认为有太多希望加快速度,这意味着如果您必须共享一个大的数据列表并希望快速访问它,那么您应该使用
数组

一次访问多个元素(例如,获取片而不是单个元素)可能会加快速度,但这取决于您想要做什么,这可能是可能的,也可能是不可能的。

Linux在子进程是
os.fork
ed时使用此功能。为了演示:

import multiprocessing as mp
import numpy as np
import logging
import os

logger = mp.log_to_stderr(logging.WARNING)

def free_memory():
    total = 0
    with open('/proc/meminfo', 'r') as f:
        for line in f:
            line = line.strip()
            if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')):
                field, amount, unit = line.split()
                amount = int(amount)
                if unit != 'kB':
                    raise ValueError(
                        'Unknown unit {u!r} in /proc/meminfo'.format(u = unit))
                total += amount
    return total

def worker(i):
    x = data[i,:].sum()    # Exercise access to data
    logger.warn('Free memory: {m}'.format(m = free_memory()))

def main():
    procs = [mp.Process(target = worker, args = (i, )) for i in range(4)]
    for proc in procs:
        proc.start()
    for proc in procs:
        proc.join()

logger.warn('Initial free: {m}'.format(m = free_memory()))
N = 15000
data = np.ones((N,N))
logger.warn('After allocating data: {m}'.format(m = free_memory()))

if __name__ == '__main__':
    main()
产生

[WARNING/MainProcess] Initial free: 2522340
[WARNING/MainProcess] After allocating data: 763248
[WARNING/Process-1] Free memory: 760852
[WARNING/Process-2] Free memory: 757652
[WARNING/Process-3] Free memory: 757264
[WARNING/Process-4] Free memory: 756760
这表明最初大约有2.5GB的可用内存。 分配15000x15000个
float64
s数组后,有763248 KB的可用空间。这大概是有道理的,因为15000**2*8字节=1.8GB,内存下降2.5GB-0.763248GB也是大约1.8GB

现在,在生成每个进程之后,空闲内存再次报告为~750MB。空闲内存没有明显减少,因此我得出结论,系统必须使用写时拷贝


结论:如果您不需要修改数据,那么在
\uuuu main\uuuu
模块的全局级别定义它是一种方便且(至少在Linux上)内存友好的方式,可以在子进程之间共享数据。

使用代理对象会将速度减慢100倍,使该方法变得无用。我想知道这是不是只为显示的情况下或没有。也许代理对象的正确用例有点不同?是的,我之所以研究这个问题,是因为我需要在进程之间共享千兆字节的数据。如果共享阵列比
Manager快得多。list
为什么不能使用它?你真的需要
列表的灵活性吗
?我想知道在重构现有应用程序时我需要付出多少努力。更改为使用列表代理非常简单。更改为共享阵列可能是可能的,但更复杂。在做这件事之前,我想确保我了解发生了什么。我已经添加了更多的解释。在重构方面需要付出多少努力取决于使用此列表的代码。如果它使用了许多
列表
特性,而这些特性在
数组中不存在,那么您将不得不更改很多代码,但是如果您已经有了一个同质序列,那么您可能不需要更改很多代码。在您的实际用例中,您是否正在修改千兆字节的数据?或者进程仅仅需要访问数据吗?@unutbu Read only就可以了。这样,您就不需要将数据作为参数传递给worker函数。只需在
\uuuu main\uuuu
模块的全局级别定义一次数据,所有子流程都可以访问它。您根本不需要mp.Manager或mp.Array来完成此任务。@unutbu这是一个非常好的观点,谢谢!不知何故,我发现在使用共享数组时可以这样做(因为将它们作为参数传递会产生错误),但遗憾的是,我未能将这一事实推广到其他情况。但让我们假设我确实需要写访问权限,因为我也希望它对我来说是清晰的。@unutbu实际上,我只是尝试过(将
def(l,t)
更改为
def(t)
并调整异步调用),看起来每个进程都存储了数据,而不是共享数据。因此,总内存使用量是使用代理对象或共享数组观察到的内存使用量的倍数。有什么想法吗?很有效
[WARNING/MainProcess] Initial free: 2522340
[WARNING/MainProcess] After allocating data: 763248
[WARNING/Process-1] Free memory: 760852
[WARNING/Process-2] Free memory: 757652
[WARNING/Process-3] Free memory: 757264
[WARNING/Process-4] Free memory: 756760