分别在并行进程中更改不同的python对象

时间:2016-12-15 12:50:10

标签: python fork shared-memory pickle python-multiprocessing

在果壳中

我想同时更改复杂的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

我得到的是分段错误错误。通过创建ArrayDictmyMethod的深度复制并将其保存到myData,我能够绕过这个错误。我真的不明白为什么这是必要的,并且经常复制我的(可能是非常大的)数组(while循环需要很长时间)对我来说似乎并不高效。但是,至少它在一定程度上起作用。然而,由于共享内存,我的程序在第3次迭代时有一些错误的行为。因此,我认为我的方式不是最佳的。

我读了herehere,可以使用multiprocessing.Array将aribtrary numpy数组保存在共享内存中。但是,我仍然需要共享整个ArrayDict,其中特别包括字典,而字典又不可选。

我怎样才能以有效的方式实现目标?以某种方式使我的对象可以选择是否可能(并且有效)?

所有解决方案必须在64位Linux上运行python 3和完全numpy / scipy支持。

修改

我发现here使用多处理“管理器”类和用户定义的代理类以某种方式共享任意对象。这会有效吗?我想利用我不需要并发访问对象,即使它们没有在主进程中处理。是否可以为我想要处理的每个对象创建一个管理器? (我可能仍然对管理者的工作方式存在一些误解。)

1 个答案:

答案 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.ValueArray表示的对象。

如果ProcessPoolExecutor不起作用,而您无法优化ArrayDict,则可能会遇到使用Manager的问题。有关如何做到这一点here的好例子。

  

通常可能会在myMethod中找到最大的性能提升。   而且,正如我所提到的,使用线程is less的开销比进程的开销少。