我正在尝试并行化我使用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] )。
如果不清楚,请告诉我,我会尝试澄清。这是我第一次尝试并行工作,所以我不确定如何继续。任何信息将不胜感激。谢谢!
答案 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_j
和x_j
替换为共享内存数组(您要将其转移到其他进程的任何内容)正常数组应该导致窗口不复制数据,但我不确定。你没有指定你所使用的平台,所以我认为安全比抱歉更好,因为多处理是windows上不同于linux的野兽。