【问题标题】:How do I pass large numpy arrays between python subprocesses without saving to disk?如何在 python 子进程之间传递大型 numpy 数组而不保存到磁盘?
【发布时间】:2011-06-29 08:39:43
【问题描述】:

有没有一种在不使用磁盘的情况下在两个 python 子进程之间传递大量数据的好方法?这是我希望完成的卡通示例:

import sys, subprocess, numpy

cmdString = """
import sys, numpy

done = False
while not done:
    cmd = raw_input()
    if cmd == 'done':
        done = True
    elif cmd == 'data':
        ##Fake data. In real life, get data from hardware.
        data = numpy.zeros(1000000, dtype=numpy.uint8)
        data.dump('data.pkl')
        sys.stdout.write('data.pkl' + '\\n')
        sys.stdout.flush()"""

proc = subprocess.Popen( #python vs. pythonw on Windows?
    [sys.executable, '-c %s'%cmdString],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE)

for i in range(3):
    proc.stdin.write('data\n')
    print proc.stdout.readline().rstrip()
    a = numpy.load('data.pkl')
    print a.shape

proc.stdin.write('done\n')

这将创建一个子进程,该子进程生成一个 numpy 数组并将该数组保存到磁盘。然后父进程从磁盘加载阵列。有效!

问题是,我们的硬件生成数据的速度是磁盘读取/写入速度的 10 倍。有没有办法将数据从一个 python 进程传输到另一个纯粹在内存中的进程,甚至可能不复制数据?我可以做类似通过引用传递的事情吗?

我第一次尝试纯粹在内存中传输数据非常糟糕:

import sys, subprocess, numpy

cmdString = """
import sys, numpy

done = False
while not done:
    cmd = raw_input()
    if cmd == 'done':
        done = True
    elif cmd == 'data':
        ##Fake data. In real life, get data from hardware.
        data = numpy.zeros(1000000, dtype=numpy.uint8)
        ##Note that this is NFG if there's a '10' in the array:
        sys.stdout.write(data.tostring() + '\\n')
        sys.stdout.flush()"""

proc = subprocess.Popen( #python vs. pythonw on Windows?
    [sys.executable, '-c %s'%cmdString],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE)

for i in range(3):
    proc.stdin.write('data\n')
    a = numpy.fromstring(proc.stdout.readline().rstrip(), dtype=numpy.uint8)
    print a.shape

proc.stdin.write('done\n')

这非常慢(比保存到磁盘慢得多)并且非常非常脆弱。一定有更好的方法!

只要数据获取进程不阻塞父应用程序,我就不会与“子进程”模块结婚。我短暂地尝试了“多处理”,但到目前为止没有成功。

背景:我们有一个硬件可以在一系列 ctypes 缓冲区中生成高达 ~2 GB/s 的数据。处理这些缓冲区的 python 代码只是处理大量的信息。我想将这种信息流与在“主”程序中同时运行的其他几个硬件协调起来,而子进程不会相互阻塞。我目前的方法是在保存到磁盘之前在子进程中将数据煮沸一点,但最好将完整的数据传递给“主”进程。

【问题讨论】:

  • 听起来线程很适合你。
  • @Gabi Purcaru 因为我对线程一无所知。随时用答案教育我!
  • 避免腌制 numpy 数组。请改用numpy.save(file, arr)。腌制一个数组会使用大量的中间内存(尤其是默认情况下),而且速度相当慢。 numpy.save 效率更高。
  • 安德鲁,你事先知道数据的总大小吗?还是最大尺寸?
  • @Joe Kington:好电话。对于约 200 MB 的数组,numpy.save() 比 numpy.dump() 节省了一点时间,(7.3 秒 -> 6.5 秒),但它减少了一半的内存使用。

标签: python numpy subprocess pass-by-reference ctypes


【解决方案1】:

使用线程。但我猜你会遇到 GIL 的问题。

改为:选择您的poison

从我使用的 MPI 实现中我知道,它们使用共享内存进行节点通信。在这种情况下,您必须编写自己的同步代码。

2 GB/s 听起来您会遇到大多数“简单”方法的问题,具体取决于您的实时限制和可用的主内存。

【讨论】:

    【解决方案2】:

    基本上,您只是想在进程之间共享一块内存并将其视为一个 numpy 数组,对吧?

    在这种情况下,看看这个(Nadav Horesh 不久前发布到 numpy-discussion,不是我的工作)。有几个类似的实现(一些更灵活),但它们基本上都使用这个原则。

    #    "Using Python, multiprocessing and NumPy/SciPy for parallel numerical computing"
    # Modified and corrected by Nadav Horesh, Mar 2010
    # No rights reserved
    
    
    import numpy as N
    import ctypes
    import multiprocessing as MP
    
    _ctypes_to_numpy = {
        ctypes.c_char   : N.dtype(N.uint8),
        ctypes.c_wchar  : N.dtype(N.int16),
        ctypes.c_byte   : N.dtype(N.int8),
        ctypes.c_ubyte  : N.dtype(N.uint8),
        ctypes.c_short  : N.dtype(N.int16),
        ctypes.c_ushort : N.dtype(N.uint16),
        ctypes.c_int    : N.dtype(N.int32),
        ctypes.c_uint   : N.dtype(N.uint32),
        ctypes.c_long   : N.dtype(N.int64),
        ctypes.c_ulong  : N.dtype(N.uint64),
        ctypes.c_float  : N.dtype(N.float32),
        ctypes.c_double : N.dtype(N.float64)}
    
    _numpy_to_ctypes = dict(zip(_ctypes_to_numpy.values(), _ctypes_to_numpy.keys()))
    
    
    def shmem_as_ndarray(raw_array, shape=None ):
    
        address = raw_array._obj._wrapper.get_address()
        size = len(raw_array)
        if (shape is None) or (N.asarray(shape).prod() != size):
            shape = (size,)
        elif type(shape) is int:
            shape = (shape,)
        else:
            shape = tuple(shape)
    
        dtype = _ctypes_to_numpy[raw_array._obj._type_]
        class Dummy(object): pass
        d = Dummy()
        d.__array_interface__ = {
            'data' : (address, False),
            'typestr' : dtype.str,
            'descr' :   dtype.descr,
            'shape' : shape,
            'strides' : None,
            'version' : 3}
        return N.asarray(d)
    
    def empty_shared_array(shape, dtype, lock=True):
        '''
        Generate an empty MP shared array given ndarray parameters
        '''
    
        if type(shape) is not int:
            shape = N.asarray(shape).prod()
        try:
            c_type = _numpy_to_ctypes[dtype]
        except KeyError:
            c_type = _numpy_to_ctypes[N.dtype(dtype)]
        return MP.Array(c_type, shape, lock=lock)
    
    def emptylike_shared_array(ndarray, lock=True):
        'Generate a empty shared array with size and dtype of a  given array'
        return empty_shared_array(ndarray.size, ndarray.dtype, lock)
    

    【讨论】:

    • 我不明白如何在这里使用它。 multiprocessing.Array() 需要在生成子进程之前创建,但在安德鲁的代码中,子进程想要创建它。我错过了什么吗?
    • @Sven - 你是对的,代码不会按原样工作。但是,调整一些东西以使其工作不应该太难(或者至少,我认为我可以让它工作而不会有太多麻烦)。给我一点,我看看能不能把一些更完整的东西拼凑起来……
    • 这看起来很有希望,期待补货。
    【解决方案3】:

    在搜索有关 Joe Kington 发布的代码的更多信息时,我发现了 numpy-sharedmem 包。从这个numpy/multiprocessing tutorial 来看,它似乎拥有相同的知识遗产(可能大部分是相同的作者?--我不确定)。

    使用 sharedmem 模块,您可以创建一个共享内存 numpy 数组(太棒了!),并将其与 multiprocessing 一起使用,如下所示:

    import sharedmem as shm
    import numpy as np
    import multiprocessing as mp
    
    def worker(q,arr):
        done = False
        while not done:
            cmd = q.get()
            if cmd == 'done':
                done = True
            elif cmd == 'data':
                ##Fake data. In real life, get data from hardware.
                rnd=np.random.randint(100)
                print('rnd={0}'.format(rnd))
                arr[:]=rnd
            q.task_done()
    
    if __name__=='__main__':
        N=10
        arr=shm.zeros(N,dtype=np.uint8)
        q=mp.JoinableQueue()    
        proc = mp.Process(target=worker, args=[q,arr])
        proc.daemon=True
        proc.start()
    
        for i in range(3):
            q.put('data')
            # Wait for the computation to finish
            q.join()   
            print arr.shape
            print(arr)
        q.put('done')
        proc.join()
    

    运行产量

    rnd=53
    (10,)
    [53 53 53 53 53 53 53 53 53 53]
    rnd=15
    (10,)
    [15 15 15 15 15 15 15 15 15 15]
    rnd=87
    (10,)
    [87 87 87 87 87 87 87 87 87 87]
    

    【讨论】:

    • 谢谢,unutbu,这看起来很棒!我试试看。
    • 对不起,我花了这么长时间才接受答案。我还没有时间自己测试它,我会在这里报告。再次感谢!
    【解决方案4】:

    从其他答案看来,numpy-sharedmem 是要走的路。

    但是,如果您需要纯 python 解决方案,或者安装扩展、cython 等是一个(大)麻烦,您可能希望使用以下代码,它是 Nadav 代码的简化版本:

    import numpy, ctypes, multiprocessing
    
    _ctypes_to_numpy = {
        ctypes.c_char   : numpy.dtype(numpy.uint8),
        ctypes.c_wchar  : numpy.dtype(numpy.int16),
        ctypes.c_byte   : numpy.dtype(numpy.int8),
        ctypes.c_ubyte  : numpy.dtype(numpy.uint8),
        ctypes.c_short  : numpy.dtype(numpy.int16),
        ctypes.c_ushort : numpy.dtype(numpy.uint16),
        ctypes.c_int    : numpy.dtype(numpy.int32),
        ctypes.c_uint   : numpy.dtype(numpy.uint32),
        ctypes.c_long   : numpy.dtype(numpy.int64),
        ctypes.c_ulong  : numpy.dtype(numpy.uint64),
        ctypes.c_float  : numpy.dtype(numpy.float32),
        ctypes.c_double : numpy.dtype(numpy.float64)}
    
    _numpy_to_ctypes = dict(zip(_ctypes_to_numpy.values(),
                                _ctypes_to_numpy.keys()))
    
    
    def shm_as_ndarray(mp_array, shape = None):
        '''Given a multiprocessing.Array, returns an ndarray pointing to
        the same data.'''
    
        # support SynchronizedArray:
        if not hasattr(mp_array, '_type_'):
            mp_array = mp_array.get_obj()
    
        dtype = _ctypes_to_numpy[mp_array._type_]
        result = numpy.frombuffer(mp_array, dtype)
    
        if shape is not None:
            result = result.reshape(shape)
    
        return numpy.asarray(result)
    
    
    def ndarray_to_shm(array, lock = False):
        '''Generate an 1D multiprocessing.Array containing the data from
        the passed ndarray.  The data will be *copied* into shared
        memory.'''
    
        array1d = array.ravel(order = 'A')
    
        try:
            c_type = _numpy_to_ctypes[array1d.dtype]
        except KeyError:
            c_type = _numpy_to_ctypes[numpy.dtype(array1d.dtype)]
    
        result = multiprocessing.Array(c_type, array1d.size, lock = lock)
        shm_as_ndarray(result)[:] = array1d
        return result
    

    你会这样使用它:

    1. 使用sa = ndarray_to_shm(a) 将ndarray a 转换为共享的multiprocessing.Array
    2. 使用multiprocessing.Process(target = somefunc, args = (sa, )(和start,可能是join)在单独的process 中调用somefunc,传递共享数组。
    3. somefunc 中,使用a = shm_as_ndarray(sa) 获取指向共享数据的ndarray。 (实际上,您可能希望在创建 sa 之后立即在原始过程中执行相同操作,以便让两个 ndarray 引用相同的数据。)

    AFAICS,您不需要将 lock 设置为 True,因为 shm_as_ndarray 无论如何都不会使用锁定。如果您需要锁定,您可以将 lock 设置为 True 并在 sa 上调用获取/释放。

    此外,如果您的数组不是一维的,您可能希望将形状与 sa 一起传输(例如,使用 args = (sa, a.shape))。

    这个解决方案的优点是它不需要额外的包或扩展模块,除了多处理(在标准库中)。

    【讨论】:

    • 我收到了PicklingError: Can't pickle <class 'multiprocessing.sharedctypes.c_double_Array_<array size>'>: attribute lookup multiprocessing.sharedctypes.c_double_Array_<array size> failed。在这里查看我的问题stackoverflow.com/questions/16303354/…
    • 偶然看到你的评论;显然,我需要检查我的通知设置。我的答案有什么我应该改变的,这对你有误导吗?
    • 那是很久以前的事了:)
    【解决方案5】:

    使用线程。 GIL 可能不会有问题。

    GIL 仅影响 Python 代码,不影响 C/Fortran/Cython 支持的库。大多数 numpy 操作和大部分 C 支持的 Scientific Python 堆栈都发布了 GIL,并且可以在多个内核上正常运行。 This blogpost 更深入地讨论 GIL 和科学 Python。

    编辑

    使用线程的简单方法包括threading 模块和multiprocessing.pool.ThreadPool

    【讨论】:

    • 这看起来很有希望!您是否建议使用“dask”模块,或者是否有更简单的方法来并行化 numpy?您能否添加一个您所想的最小代码示例?
    • 我添加了一个快速编辑,将人们指向 threadingmultiprocessing.pool.ThreadPool 两者都有一些简单的异步执行函数的方法。
    【解决方案6】:

    可以考虑的一种可能性是使用RAM drive 来临时存储要在进程之间共享的文件。 RAM 驱动器是将 RAM 的一部分视为逻辑硬盘驱动器,可以像使用普通驱动器一样在其中写入/读取文件,但以 RAM 读取/写入速度。

    本文介绍了使用 ImDisk 软件(适用于 MS Win)创建此类磁盘并获得 6-10 GB/秒的文件读/写速度: https://www.tekrevue.com/tip/create-10-gbs-ram-disk-windows/

    Ubuntu 中的一个例子: https://askubuntu.com/questions/152868/how-do-i-make-a-ram-disk#152871

    另一个值得注意的好处是可以使用这种方法传递具有任意格式的文件:例如Picke、JSON、XML、CSV、HDF5 等...

    请记住,存储在 RAM 磁盘上的所有内容都会在重启时被擦除。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-05
      • 2015-05-20
      • 2018-08-31
      • 2018-10-26
      • 1970-01-01
      • 2021-12-29
      • 2022-10-19
      相关资源
      最近更新 更多