Как передать большие массивы numpy между подпроцессами python без сохранения на диск?

Есть ли хороший способ передать большой кусок данных между двумя подпроцессами 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 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')

Это очень медленный (гораздо медленнее, чем сохранение на диске) и очень, очень хрупкий. Там должен быть лучший способ!

Я не женат на модуле "subprocess", если процесс обработки данных не блокирует родительское приложение. Я кратко попробовал "многопроцессорную обработку", но пока не успел.

Фон: у нас есть часть оборудования, которое генерирует до ~ 2 ГБ/с данных в серии буферов ctypes. Код python для обработки этих буферов имеет свои руки, полностью связанные с потоком информации. Я хочу координировать этот поток информации с несколькими другими аппаратными средствами, запущенными одновременно в "основной" программе, без подпроцессов, блокирующих друг друга. Мой текущий подход заключается в том, чтобы немного свернуть данные в подпроцессе перед сохранением на диск, но было бы неплохо передать полный monty в процесс "master".

Ответ 1

В то время как googling для получения дополнительной информации о коде Joe Kington опубликовал, я нашел numpy-sharedmem. Судя по этому учебному курсу numpy/multiprocessing, он, похоже, имеет одно и то же интеллектуальное наследие (возможно, в основном те же авторы?), Я не уверен).

Используя модуль sharedmem, вы можете создать массив numpy с общей памятью (awesome!) и использовать его с 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]

Ответ 2

В принципе, вы просто хотите разделить блок памяти между процессами и просмотреть его как массив numpy, правильно?

В этом случае взгляните на это (Добавлено в numpy-обсуждение Надава Хореша некоторое время назад, а не моя работа). Есть несколько аналогичных реализаций (некоторые более гибкие), но все они по существу используют этот принцип.

#    "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)

Ответ 3

Из других ответов кажется, что 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

Вы бы использовали его следующим образом:

  • Используйте sa = ndarray_to_shm(a) для преобразования ndarray a в общий multiprocessing.Array.
  • Используйте multiprocessing.Process(target = somefunc, args = (sa, )start, возможно join), чтобы вызвать somefunc в отдельном process, передавая общий массив.
  • В somefunc используйте a = shm_as_ndarray(sa), чтобы получить ndarray, указывающий на общие данные. (Фактически, вы можете сделать то же самое в первоначальном процессе сразу после создания sa, чтобы иметь два ndarrays, ссылающихся на одни и те же данные.)

AFAICS, вам не нужно устанавливать блокировку на True, так как shm_as_ndarray не будет использовать блокировку в любом случае. Если вам нужна блокировка, вы должны установить блокировку на True и вызвать получение/освобождение на sa.

Кроме того, если ваш массив не является одномерным, вы можете перенести форму вместе с sa (например, использовать args = (sa, a.shape)).

Это решение имеет то преимущество, что ему не нужны дополнительные пакеты или модули расширения, за исключением многопроцессорности (которая находится в стандартной библиотеке).

Ответ 4

Использовать потоки. Но я думаю, что у вас будут проблемы с GIL.

Вместо этого: выберите яд.

Я знаю из реализации MPI, с которыми я работаю, что они используют общую память для on- node -коммуникаций. В этом случае вам придется закодировать свою собственную синхронизацию.

2 ГБ/с, похоже, у вас появятся проблемы с большинством "простых" методов, в зависимости от ваших ограничений в реальном времени и доступной основной памяти.

Ответ 5

Использовать потоки. У вас, вероятно, не будет проблем с GIL.

GIL влияет только на код Python, а не на библиотеки C/Fortran/Cython. Большинство операций с numpy и хороший кусок C-backed Scientific Python стека выпускают GIL и могут прекрасно работать на нескольких ядрах. В этом blogpost более подробно обсуждается GIL и научный Python.

Изменить

Простые способы использования потоков включают модуль threading и multiprocessing.pool.ThreadPool.

Ответ 6

Можно рассмотреть возможность использовать RAM-диск для временного хранения файлов, которые будут совместно использоваться между процессами. Привод RAM - это место, где часть ОЗУ обрабатывается как логический жесткий диск, к которому файлы могут быть записаны/прочитаны так же, как на обычном диске, но при скорости чтения/записи RAM.

В этой статье описывается использование программного обеспечения ImDisk (для MS Win) для создания такого диска и получения скорости чтения/записи файлов 6-10 гигабайт в секунду: https://www.tekrevue.com/tip/create-10-gbs-ram-disk-windows/

Пример в Ubuntu: https://askubuntu.com/info/152868/how-do-i-make-a-ram-disk#152871

Другим важным преимуществом является то, что файлы с произвольными форматами могут быть переданы с помощью такого метода: например. Picke, JSON, XML, CSV, HDF5 и т.д.

Имейте в виду, что все, что хранится на RAM-диске, стирается при перезагрузке.