在多处理过程之间共享大型只读Numpy数组

时间:2013-07-22 10:31:17

标签: python numpy multiprocessing shared-memory

我有一个60GB的SciPy数组(矩阵)我必须在5 + multiprocessing Process个对象之间共享。我见过numpy-sharedmem并在SciPy列表中阅读this discussion。似乎有两种方法 - numpy-sharedmem并使用multiprocessing.RawArray()并将NumPy dtype映射到ctype s。现在,numpy-sharedmem似乎是要走的路,但我还没有看到一个很好的参考示例。我不需要任何类型的锁,因为数组(实际上是矩阵)将是只读的。现在,由于它的大小,我想避免副本。 听起来像正确的方法是将数组的副本创建为sharedmem数组,然后将其传递给Process个对象?几个具体问题:

  1. 将sharedmem句柄实际传递给子Process() es的最佳方法是什么?我是否需要一个队列来传递一个阵列?管道会更好吗?我可以将它作为参数传递给Process()子类的init(我假设它被腌制)吗?

  2. 在我上面提到的讨论中,提到numpy-sharedmem不是64位安全的?我肯定使用的是一些非32位可寻址的结构。

  3. 是否有RawArray()方法的权衡?更慢,更忙?

  4. 我是否需要numpy-sharedmem方法的任何ctype-to-dtype映射?

  5. 有没有人有一些OpenSource代码这样做的例子?我是一个非常亲力实践的人,如果没有任何好的例子,很难让它工作。

  6. 如果我可以提供任何其他信息以帮助其他人澄清这一点,请发表评论,我将添加。谢谢!

    这需要在Ubuntu Linux和 Maybe Mac OS上运行,但可移植性不是一个大问题。

6 个答案:

答案 0 :(得分:32)

如果您使用的是Linux(或任何符合POSIX标准的系统),则可以将此数组定义为全局变量。 multiprocessing在Linux启动新的子进程时正在使用fork()。新生成的子进程会自动与其父进程共享内存,只要它不会更改它(copy-on-write机制)。

因为你说“我不需要任何类型的锁,因为数组(实际上是一个矩阵)将是只读的”利用这种行为将是一种非常简单但非常有效的方法:所有的孩子当读取这个大的numpy数组时,进程将访问物理内存中的相同数据。

不要将您的数组交给Process()构造函数,这会向{child} multiprocessing指示pickle给孩子的数据,这在您的情况下效率极低或不可能。在Linux上,在fork()之后,子项是使用相同物理内存的父项的精确副本,因此您需要做的就是确保可以从{{ 1}}你交给target的函数。这通常可以通过“全局”变量来实现。

示例代码:

Process()

在Windows上 - 不支持from multiprocessing import Process from numpy import random global_array = random.random(10**4) def child(): print sum(global_array) def main(): processes = [Process(target=child) for _ in xrange(10)] for p in processes: p.start() for p in processes: p.join() if __name__ == "__main__": main() - fork()正在使用win32 API调用multiprocessing。它从任何给定的可执行文件创建一个全新的进程。这就是为什么在Windows上,如果需要在父运行时期间创建的数据,则需要 来向子进行数据处理。

答案 1 :(得分:25)

@Velimir Mlaker给出了一个很好的答案。我想我可以添加一些评论和一个小例子。

(我找不到很多关于sharedmem的文档 - 这些是我自己实验的结果。)

  1. 您是否需要在子进程启动时或启动后传递句柄?如果它只是前者,您可以使用targetargs Process参数。这可能比使用全局变量更好。
  2. 在您链接的讨论页面中,似乎有一段时间以前对sharedmem添加了对64位Linux的支持,因此它可能不是问题。
  3. 我不知道这个。
  4. 否。请参阅下面的示例。
  5. 实施例

    #!/usr/bin/env python
    from multiprocessing import Process
    import sharedmem
    import numpy
    
    def do_work(data, start):
        data[start] = 0;
    
    def split_work(num):
        n = 20
        width  = n/num
        shared = sharedmem.empty(n)
        shared[:] = numpy.random.rand(1, n)[0]
        print "values are %s" % shared
    
        processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
        for p in processes:
            p.start()
        for p in processes:
            p.join()
    
        print "values are %s" % shared
        print "type is %s" % type(shared[0])
    
    if __name__ == '__main__':
        split_work(4)
    

    输出

    values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
      0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
      0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
      0.7292129   0.06063283]
    values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
      0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
      0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
      0.7292129   0.06063283]
    type is <type 'numpy.float64'>
    

    related question可能有用。

答案 2 :(得分:19)

您可能对我写的一小段代码感兴趣:github.com/vmlaker/benchmark-sharedmem

唯一感兴趣的文件是main.py。它是numpy-sharedmem的基准 - 代码只是通过Pipe将数组(numpysharedmem)传递给生成的进程。工人只需在数据上调用sum()即可。我只对比较两种实现之间的数据通信时间感兴趣。

我还写了另一个更复杂的代码:github.com/vmlaker/sherlock

这里我使用numpy-sharedmem模块与OpenCV进行实时图像处理 - 根据OpenCV较新的cv2 API,图像是NumPy数组。图像(实际上是它们的引用)是通过从multiprocessing.Manager创建的字典对象在进程之间共享的(而不是使用Queue或Pipe。)与使用普通的NumPy数组相比,我获得了很大的性能提升。

管道与队列

根据我的经验,IPC with Pipe比Queue更快。这是有道理的,因为Queue增加了锁定,使其对多个生产者/消费者来说是安全的。管没有。但是如果你只有两个进程来回交谈,那么使用Pipe是安全的,或者,正如文档所读:

  

......同时使用管道不同端的进程不存在腐败风险。

sharedmem安全

sharedmem模块的主要问题是在程序退出时出现内存泄漏的可能性。这在冗长的讨论here中有所描述。尽管在2011年4月10日,Sturla提到了内存泄漏的修复,但自那时起我仍然经历过泄漏,使用Repos,Sturla Molden自己的GitHub(github.com/sturlamolden/sharedmem-numpy)和Chris Lee-Messer的Bitbucket({{3} })。

答案 3 :(得分:11)

如果您的阵列很大,可以使用numpy.memmap。例如,如果您有一个存储在磁盘中的数组,比如'test.array',即使在“写入”模式下,您也可以使用同时进程来访问其中的数据,但由于您只需要“读取”模式,因此您的情况更简单

创建数组:

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

然后,您可以像使用普通数组一样填充此数组。例如:

a[:10,:100]=1.
a[10:,100:]=2.

删除变量a时,数据将存储到磁盘中。

稍后您可以使用多个进程来访问test.array中的数据:

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

相关答案:

答案 4 :(得分:3)

您可能还会发现查看pyro的文档很有用,就好像您可以对您的任务进行适当分区,您可以使用它来执行不同计算机上的不同部分以及同一部分中的不同核心机。

答案 5 :(得分:0)

为什么不使用多线程? 主进程的资源可以由其线程本地共享,因此多线程显然是共享主进程拥有的对象的更好方法。

如果您担心python的GIL机制,也许可以求助于numba的{​​{3}}。