在果壳中
我想同时更改复杂的python对象,每个对象只由一个进程处理。我怎么能这样做(效率最高)?实施某种酸洗支持会有帮助吗?这会有效吗?
完整问题
我有一个python数据结构ArrayDict
,它基本上由一个numpy
数组和一个字典组成,并将任意索引映射到数组中的行。就我而言,所有键都是整数。
a = ArrayDict()
a[1234] = 12.5
a[10] = 3
print(a[1234]) #12.5
print(a[10]) # 3.0
print(a[1234] == a.array[a.indexDict[1234]]) #true
现在我有多个这样的ArrayDict
,并希望将它们填入myMethod(arrayDict, params)
。由于myMethod
很贵,我想并行运行它。请注意,myMethod
可能会向arrayDict
添加许多行。每个流程都会改变自己的ArrayDict
。我不需要同时访问ArrayDict
。
在myMethod
中,我更改arrayDict
中的条目(即我更改内部numpy
数组),我向arrayDict
添加条目(即,我在字典中添加另一个索引并在内部数组中写入一个新值。最后,我希望能够在arrayDict
的内部numpy
数组变得太小时进行交换。这不会经常发生,如果没有更好的解决方案,我可以在程序的非并行部分执行此操作。即使没有阵列交换,我自己的尝试也没有成功。
我花了几天时间研究共享内存和python的multiprocessing模块。由于我最终将在linux上工作,因此任务似乎相当简单:系统调用fork()
允许有效地处理参数的副本。我的想法是在其自己的进程中更改每个ArrayDict
,返回对象的更改版本,并覆盖原始对象。为了节省内存并保存复制工作,我还使用sharedmem数组将数据存储在ArrayDict
中。我知道字典仍然必须被复制。
from sharedmem import sharedmem
import numpy as np
n = ... # length of the data array
myData = np.empty(n, dtype=object)
myData[:] = [ArrayDict() for _ in range(n)]
done = False
while not done:
consideredData = ... # numpy boolean array of length
# n with True at the index of
# considered data
args = ... # numpy array containing arguments
# for myMethod
with sharedmem.MapReduce() as pool:
results = pool.map(myMethod,
list(zip(myData[considered],
args[considered])),
star=True)
myData[considered] = results
done = ... # depends on what happens in
# myMethod
我得到的是分段错误错误。通过创建ArrayDict
到myMethod
的深度复制并将其保存到myData
,我能够绕过这个错误。我真的不明白为什么这是必要的,并且经常复制我的(可能是非常大的)数组(while循环需要很长时间)对我来说似乎并不高效。但是,至少它在一定程度上起作用。然而,由于共享内存,我的程序在第3次迭代时有一些错误的行为。因此,我认为我的方式不是最佳的。
我读了here和here,可以使用multiprocessing.Array
将aribtrary numpy数组保存在共享内存中。但是,我仍然需要共享整个ArrayDict
,其中特别包括字典,而字典又不可选。
我怎样才能以有效的方式实现目标?以某种方式使我的对象可以选择是否可能(并且有效)?
所有解决方案必须在64位Linux上运行python 3和完全numpy / scipy支持。
修改
我发现here使用多处理“管理器”类和用户定义的代理类以某种方式共享任意对象。这会有效吗?我想利用我不需要并发访问对象,即使它们没有在主进程中处理。是否可以为我想要处理的每个对象创建一个管理器? (我可能仍然对管理者的工作方式存在一些误解。)
答案 0 :(得分:4)
这似乎是一个相当复杂的类,我无法完全预测这个解决方案是否适用于您的情况。对这种复杂类的简单折衷是使用ProcessPoolExecutor
。
如果这不能回答你的问题,那么用最小的,有效的例子就可以了。
from concurrent.futures import ProcessPoolExecutor
import numpy as np
class ArrayDict ():
keys = None
vals = None
def __init__ (self):
self.keys = dict ()
self.vals = np.random.rand (1000)
def __str__ (self):
return "keys: " + str(self.keys) + ", vals: " + str(self.vals.mean())
def myMethod (ad, args):
print ("starting:", ad)
if __name__ == '__main__':
l = [ArrayDict() for _ in range (5)]
args = [2, 3, 4, 1, 3]
with ProcessPoolExecutor (max_workers = 2) as ex:
d = ex.map (myMethod, l, args)
当对象发送到子进程时,对象为cloned,您需要返回结果(因为对对象的更改不会传播回主进程)并处理您希望如何存储它们。
请注意,对类变量的更改将传播到其中的其他对象 相同的过程,例如如果您有多个任务而不是进程,则更改为类 变量将在同一进程中运行的实例之间共享。这通常是不受欢迎的行为。
这是并行化的高级接口。 ProcessPoolExecutor
使用multiprocessing
模块,只能与pickable objects一起使用。我怀疑ProcessPoolExecutor
的效果与"sharing state between processes"相似。在ProcessPoolExecutor
is using multiprocessing.Process
下,应该表现出与Pool
类似的效果(使用地图时使用very long iterables除外)。 ProcessPoolExecutor
似乎确实是python中并发任务的未来API。
如果可以的话,使用ThreadPoolExecutor
(可以只换ProcessPoolExecutor
)通常会更快。在这种情况下,对象在进程之间共享,对一个进程的更新将传播回主线程。
如上所述,最快的选项可能是重新构建ArrayDict
,以便它只使用可由multiprocessing.Value
或Array
表示的对象。
如果ProcessPoolExecutor
不起作用,而您无法优化ArrayDict
,则可能会遇到使用Manager
的问题。有关如何做到这一点here的好例子。
通常可能会在
myMethod
中找到最大的性能提升。 而且,正如我所提到的,使用线程is less的开销比进程的开销少。