并行组装Numpy数组

时间:2013-11-16 16:50:08

标签: python arrays numpy parallel-processing

我正在尝试并行化我使用Multiprocessing和Pool.map()命令的算法。我遇到了一个问题,并希望有人能指出我正确的方向。

设x表示N行1列的数组,初始化为0的向量。设C表示长度为N的数组2.通过使用来自C的某些子集的信息(进行一些数学运算)迭代地构造向量x。作为大型for循环的代码(未并行化)大致如下所示:

for j in range(0,N)
   #indx_j will have n_j <<N entries 
   indx_j = build_indices(C,j)

   #x_j will be entries to be added to vector x at indices indx_j
   #This part is time consuming
   x_j = build_x_j(indx_j,C)

   #Add x_j into entries of x
   x[indx_j] = x[indx_j] + x_j

我能够使用多处理模块并使用pool.map来并行化,以消除大的for循环。我编写了一个执行上述计算的函数,除了将x_j添加到x [indx_j]的步骤。而并行化函数则返回两个数据集:x_j和indx_j。在计算完这些之后,我通过对j = 0,N执行x [indx_j] = x [indx_j] + x_j计算来运行for循环(非并行)来构建x。

我的方法的缺点是pool.map操作返回N对数组x_j和indx_j的巨大列表。其中x_j和indx_j都是n_j乘以1个向量(n_j <&lt; N)。对于大N(N> 20,000),这占用了太多的记忆。这是我的问题:我可以以某种方式并行地执行构造操作x [indx_j] = x [indx_j] + x_j。在我看来,pool.map()中的每个进程都必须能够与向量x进行交互。我是否将x放在某种共享内存中?我怎么做这样的事情?我怀疑这必须以某种方式实现,因为我假设人们一直在为有限元方法并行组装矩阵。如何在不出现某些问题的情况下让多个进程与向量进行交互?我担心,对于j = 20和j = 23,如果它们同时发生,它们可能会尝试添加到x [indx_20] = x [indx_20] + x_20并同时x [indx_30] = x [indx_30] + x_30并且可能会发生一些错误。我也不知道如何通过pool.map()完成这个计算(我不认为我可以输入x作为输入,因为它会在每个进程后改变)。

我不确定它是否重要,但是indx_j集合将具有非平凡的交​​集(例如,indx_1和indx_2可能具有索引[1,2,3]和[3,4,5] )。

如果不清楚,请告诉我,我会尝试澄清。这是我第一次尝试并行工作,所以我不确定如何继续。任何信息将不胜感激。谢谢!

1 个答案:

答案 0 :(得分:1)

我不知道如果我有资格就共享内存数组的主题提供适当的建议,但我最近有类似的需要在python中跨进程共享数组,并遇到了一个小的自定义numpy.ndarray实现使用multiprocessing中的共享ctypes在numpy中共享内存数组。以下是代码的链接:shmarray.py。它的作用就像普通数组一样,除了底层数据存储在共享内存中,这意味着单独的进程可以读取和写入同一个数组。

使用共享内存阵列

在线程中,线程可用的所有信息(全局和本地命名空间)可以在有权访问它的所有其他线程之间共享,但在多处理中,数据不易访问。在Linux上,数据可供读取,但无法写入。相反,当写入完成时,数据被复制然后写入,这意味着没有其他进程可以看到这些更改。但是,如果要写入的内存是共享内存,则不会复制它。这意味着使用shmarray,我们可以做类似于我们进行线程化的方式,具有多处理的真正并行性。访问共享内存数组的一种方法是使用子类。我知道你当前正在使用Pool.map(),但我觉得受到地图工作方式的限制,特别是在处理n维数组时。 Pool.map()并不是真的设计用于numpy样式的界面,至少我认为它不容易。这是一个简单的想法,您将为j中的每个N生成一个流程:

import numpy as np
import shmarray
import multiprocessing

class Worker(multiprocessing.Process):
    def __init__(self, j, C, x):
        multiprocessing.Process.__init__()
        self.shared_x = x
        self.C = C
        self.j = j

    def run(self):
         #Your Stuff
         #indx_j will have n_j <<N entries 
         indx_j = build_indices(self.C,self.j)

         #x_j will be entries to be added to vector x at indices indx_j
         x_j = build_x_j(indx_j,self.C)

         #Add x_j into entries of x
         self.shared_x[indx_j] = self.shared_x[indx_j] + x_j

  #And then actually do the work
  N = #What ever N should be
  x = shmarray.zeros(shape=(N,1))
  C = #What ever C is, doesn't need to be shared mem, since no writing is happening

  procs = []
  for j in range(N):
      proc = Worker(j, C, x)
      procs.append(proc)
      proc.start()

  #And then join() the processes with the main process
  for proc in procs:
      proc.join()

自定义流程池和队列

所以这可能会起作用,但如果你只有几个核心,那么产生几千个进程并没有任何用处。我处理这个问题的方法是在我的进程之间实现一个Queue系统。也就是说,我们有Queue主进程填充j,然后一对工作进程从Queue获取数字并使用它,请注意,通过实现此功能,您实际上正在执行Pool所做的事情。另请注意,我们实际上将使用multiprocessing.JoinableQueue,因为它允许使用join()等待队列清空。

实际上并不难实现这一点,我们必须稍微修改一下Subclass以及我们如何使用它。     导入numpy为np     进口shmarray     导入多处理

class Worker(multiprocessing.Process):
    def __init__(self, C, x, job_queue):
        multiprocessing.Process.__init__()
        self.shared_x = x
        self.C = C
        self.job_queue = job_queue

    def run(self):
         #New Queue Stuff
         j = None
         while j!='kill':  #this is how I kill processes with queues, there might be a cleaner way.
             j = self.job_queue.get()  #gets a job from the queue if there is one, otherwise blocks.
             if j!='kill':
                 #Your Stuff
                 indx_j = build_indices(self.C,j)
                 x_j = build_x_j(indx_j,self.C)
                 self.shared_x[indx_j] = self.shared_x[indx_j] + x_j

                 #This tells the queue that the job that was pulled from it
                 #Has been completed (we need this for queue.join())
             self.job_queue.task_done()

  #The way we interact has changed, now we need to define a job queue
  job_queue = multiprocessing.JoinableQueue()
  N = #What ever N should be
  x = shmarray.zeros(shape=(N,1))
  C = #What ever C is, doesn't need to be shared mem, since no writing is happening

  procs = []
  proc_count = multiprocessing.cpu_count() # create as many procs as cores
  for _ in range(proc_count):
      proc = Worker(C, x, job_queue) #now we pass the job queue instead
      procs.append(proc)
      proc.start()

  #all the workers are just waiting for jobs now.
  for j in range(N):
      job_queue.put(j)

  job_queue.join() #this blocks the main process until the queue has been emptied

  #Now if you want to kill all the processes, just send a 'kill'
  #job for each process.
  for proc in procs:
      job_queue.put('kill')
  job_queue.join()

最后,我真的不能说这将如何处理重叠索引的同时写入。最糟糕的情况是,如果两件事情同时写入并且事情被破坏/崩溃,你可能会遇到严重问题(我不是专家,所以我真的不知道是否会发生这种情况)。最好的情况,因为你只是在做补充,操作顺序并不重要,一切运行顺利。如果它没有顺利运行,我的建议是创建一个专门执行数组赋值的第二个自定义Process子类。要实现这一点,您需要传递一个作业队列和一个&#39;输出&#39;队列到Worker子类。在while循环中,你应该有一个`output_queue.put((indx_j,x_j))。 注意:如果您将这些放入队列中,它们会被腌制,这可能会很慢。如果可以在使用put之前,我建议将它们作为共享内存阵列。在某些情况下腌制它们可能会更快,但我没有对它进行测试。要在生成它们时分配它们,您需要让Assigner进程从队列中读取这些值作为作业并应用它们,这样工作循环基本上是:

def run(self):
    job = None
    while job!='kill':
        job = self.job_queue.get()
        if job!='kill':
            indx_j, x_j = job
            #Note this is the process which really needs access to the X array.
            self.x[indx_j] += x_j
        self.job_queue.task_done()

这最后一个解决方案可能比在工作线程中执行赋值要慢,但是如果你这样做,你就不用担心竞争条件了,因为你可以用掉{{1生成它们时的值和indx_j值,而不是等到所有值都完成。

Windows注意事项

我没有在Windows上做任何这项工作,所以我不是100%肯定,但我相信上面的代码会占用大量内存,因为Windows 实现副本用于产生独立过程的写入系统。基本上,Windows将复制进程在从主进程生成新进程时所需的所有信息。要解决此问题,我 认为 将所有x_jx_j替换为共享内存数组(您要将其转移到其他进程的任何内容)正常数组应该导致窗口复制数据,但我不确定。你没有指定你所使用的平台,所以我认为安全比抱歉更好,因为多处理是windows上不同于linux的野兽。