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


88

我有一个60GB的SciPy阵列(矩阵),必须在5个以上的multiprocessing Process对象之间共享。我看过numpy-sharedmem并在SciPy列表上阅读了此讨论。似乎有两种方法numpy-sharedmem-使用amultiprocessing.RawArray()和将NumPy dtypes映射到ctypes。现在,这numpy-sharedmem似乎是要走的路,但是我还没有看到一个很好的参考示例。我不需要任何类型的锁,因为数组(实际上是矩阵)将是只读的。现在,由于它的大小,我想避免复制。这听起来像是正确的方法是创建唯一的数组作为副本sharedmem数组,然后将它传递给Process对象?几个特定的​​问题:

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

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

  3. RawArray()方法是否需要权衡?慢一点,孩子吗?

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

  5. 有没有人举一些开源代码的例子呢?我是一个非常动手的知识,如果没有任何好的榜样,很难使它成功。

如果有任何其他我可以提供的信息,以帮助其他人进行澄清,请发表评论,然后添加。谢谢!

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


1
如果不同的进程要写入该数组,则期望multiprocessing为每个进程复制整个过程。
tiago

3
@tiago:“我不需要任何类型的锁,因为数组(实际上是矩阵)将是只读的”
Jan-Philip Gehrcke博士

1
@tiago:而且,只要未明确告知(通过的参数target_function)多处理就不会复制。操作系统将仅在修改后将父级内存的一部分复制到子级内存中。
Jan-Philip Gehrcke博士


我之前曾问过几个 问题。我的解决方案可以在这里找到:github.com/david-hoffman/peaks/blob/…(对不起,代码是一场灾难)。
David Hoffman

Answers:


30

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

(我在sharedmem上找不到太多文档-这些是我自己实验的结果。)

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

#!/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'>

这个相关的问题可能有用。


37

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

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

不要将数组交给Process()构造函数,这将把数据指示multiprocessingpickle子级,这在您的情况下是极其低效或不可能的。在Linux上,fork()子对象之后是使用相同物理内存的父对象的精确副本,因此您所要做的就是确保从target传递给函数的函数中可以访问“包含”矩阵的Python变量Process()。通常,您可以使用“全局”变量来实现此目的。

示例代码:

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()

在Windows上-不支持fork()-multiprocessing使用Win32 API调用CreateProcess。它从任何给定的可执行文件创建一个全新的过程。因此,在Windows上,如果需要在父级运行时创建的数据,则需要将数据腌制给子级。


3
写入时复制将复制包含引用计数器的页面(因此,每个派生的python将具有其自己的引用计数器),但不会复制整个数据数组。
robince

1
我要补充一点,我在模块级别的变量上比在全局变量上获得了更多的成功……即,在派生之前将变量添加到全局范围内的模块中
robince 2013年

5
在此问题/答案上绊脚的人要当心:如果您碰巧将OpenBLAS链接的Numpy用于其多线程操作,请确保在使用时禁用其多线程(导出OPENBLAS_NUM_THREADS = 1),multiprocessing否则子进程可能会挂起(通常在共享的全局阵列/矩阵上执行线性代数运算时,通常使用一个处理器的1 / n(而不是n个处理器)。与OpenBLAS已知多线程冲突似乎已扩展至Pythonmultiprocessing
Dologan

1
谁能解释为什么python不仅仅使用OSfork传递给的参数Process,而不是序列化它们?也就是说,不能在调用之前fork其应用于父进程,以便仍可从OS获得参数值吗?似乎比序列化更有效? child
最高

2
我们都知道这fork()在Windows上不可用,这已经在我的回答中提到,并在注释中多次提到。我知道这是您的第一个问题,我在之上回答了四条评论:“折衷方案是默认情况下在两个平台上使用相同的参数传递方法,以实现更好的可维护性并确保行为平等。” 两种方法都有其优点和缺点,这就是为什么在Python 3中,用户选择该方法具有更大的灵活性。此讨论没有讨论的细节是没有成果的,在这里我们不应该这样做。
Jan-Philip Gehrcke博士2015年

24

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

唯一感兴趣的文件是main.py。它是一个基准numpy的-sharedmem -的代码只是简单地传递阵列(无论是numpysharedmem通过Pipe)给生成的进程。工人们只是调用sum()数据。我只对比较两个实现之间的数据通信时间感兴趣。

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

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

管道与队列

以我的经验,使用Pipe的IPC比Queue更快。这很有意义,因为Queue添加了锁定功能以使其对多个生产者/消费者来说都是安全的。管道没有。但是,如果您只有两个来回交互的过程,则使用Pipe是安全的,或者,如文档所示:

...不会因为同时使用管道的不同末端的过程而造成损坏的风险。

sharedmem 安全

sharedmem模块的主要问题是程序退出时可能会导致内存泄漏。这在这里的冗长讨论中进行了描述。尽管在2011年4月10日Sturla提到了内存泄漏的修复程序,但此后我仍然经历过泄漏,使用这两个仓库,分别是GitHub上的Sturla Molden自己的仓库github.com/sturlamolden/sharedmem-numpy)和Chris Lee-Messer的Bitbucket(bitbucket.org/cleemesser/numpy-sharedmem)。


谢谢,非常非常有益。不过,内存泄漏sharedmem听起来很重要。解决这个问题有什么线索吗?
2013年

1
除了注意到泄漏之外,我还没有在代码中寻找它。我在上面的“ sharedmem安全性”下的sharedmem模块中添加了两个开放源代码存储库的保存者,以供参考。
Velimir Mlaker

14

如果数组那么大,可以使用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))

相关答案:


3

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


0

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

如果您担心python的GIL机制,则可以使用nogilnumba

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.