Совместное Использование Большого Массива Numpy Только Для Чтения Между Многопроцессорными Процессами


у меня есть 60 ГБ SciPy массив (Матрица) я должен разделить между 5+ multiprocessingProcess объекты. Я видел numpy-sharedmem и читал эта дискуссия в списке SciPy. Есть два подхода--numpy-sharedmem и с помощью multiprocessing.RawArray() и отображение NumPy dtypeь ctypeы. Теперь, numpy-sharedmem Кажется, это путь, но я еще не видел хороший пример ссылки. Мне не нужны никакие блокировки, так как массив (фактически матрица) будет доступен только для чтения. Теперь, из-за его размера, я хотел бы чтобы избежать копирования. Это звучит как правильный метод заключается в создании только копия массива в виде sharedmem массив, а затем передать его в Process объекты? Несколько конкретных вопросов:

  1. какой самый лучший способ, на самом деле проходят sharedmem ручки для Sub-Process()Эс? Нужна ли мне очередь только для передачи одного массива? Может быть, лучше трубка? Могу ли я просто передать его в качестве аргумента Process() подкласс init (где я предполагаю это маринованный)?

  2. в обсуждении, которое я связал выше, есть упоминание о numpy-sharedmem не будучи 64bit-безопасным? Я определенно использую некоторые структуры, которые не являются 32-разрядными адресными.

  3. есть ли компромисс в RawArray() подход? Медленнее, педераст?

  4. нужен ли мне ctype Для к dtype отображение для библиотеки numpy-sharedmem способ?

  5. есть ли у кого-нибудь пример некоторого кода с открытым исходным кодом делать это? Я очень практический ученый, и трудно заставить это работать без какого-либо хорошего примера, чтобы посмотреть.

если есть какая-либо дополнительная информация, которую я могу предоставить, чтобы помочь прояснить это для других, прокомментируйте, и я добавлю. Спасибо!

это должно работать на Ubuntu Linux и может быть Mac OS, но переносимость не является огромной проблемой.

5 70

5 ответов:

@Velimir Mlaker дал отличный ответ. Я подумал, что могу добавить несколько комментариев и крошечный пример.

(Я не смог найти много документации по sharedmem-это результаты моих собственных экспериментов.)

  1. нужно ли передавать дескрипторы при запуске подпроцесса или после его запуска? Если это только первый, вы можете просто использовать target и args аргументы Process. Это потенциально лучше, чем использование глобального переменная.
  2. на странице обсуждения, которую вы связали, похоже, что поддержка 64-разрядного Linux была добавлена в sharedmem некоторое время назад, поэтому это может быть не проблема.
  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'>

этой вопрос может быть полезным.

если вы используете Linux (или любую систему, совместимую с POSIX), вы можете определить этот массив как глобальную переменную. multiprocessing С помощью fork() в Linux, когда он запускает новый дочерний процесс. Вновь созданный дочерний процесс автоматически делится памятью со своим родителем до тех пор, пока он не изменит ее (копировать на запись механизм).

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

не передавайте свой массив в Process() конструктор, это поручить multiprocessing to pickle данные для ребенка, которые были бы крайне неэффективны или невозможны в вашем случае. На Linux, сразу после fork() ребенок является точной копией родителя, используя ту же физическую память, так что все, что вам нужно сделать, это убедитесь, что переменная Python, содержащая матрицу, доступна из target функция, которую вы передаете 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 один требуются засолить данные к ребенка, если нужны данные, которые были созданы во время выполнения родителя.

вас может заинтересовать крошечный кусочек кода, который я написал:github.com/vmlaker/benchmark-sharedmem

единственный файл, проценты main.py. Это эталон numpy-sharedmem -- код просто передает массивы (либо numpy или sharedmem) для порожденных процессов, через трубу. Рабочие просто звонят sum() на данных. Меня интересует только сравнение времени передачи данных между двумя реализациями.

я тоже писал другой, более сложный код: github.com/vmlaker/sherlock.

здесь я использую numpy-sharedmem модуль для обработки изображений в реальном времени с помощью OpenCV -- изображения представляют собой массивы NumPy, согласно более новой версии OpenCV cv2 API. Изображения, фактически ссылки на них, разделяются между процессами через объект словаря, созданный из multiprocessing.Manager (в отличие от использования очереди или трубы.) Я получаю большие улучшения производительности по сравнению с использование простых массивов NumPy.

труба против очереди:

по моему опыту, IPC с трубой быстрее, чем очередь. И это имеет смысл, так как очередь добавляет блокировку, чтобы сделать ее безопасной для нескольких производителей/потребителей. Но если у вас есть только два процесса, говорящие взад и вперед, безопасно использовать Pipe, или, как говорится в документах:

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

sharedmem безопасность:

главная проблема sharedmem модуль-это возможность утечки памяти при некрасивом выходе из программы. Это описано в длительной дискуссии здесь. Хотя 10 апреля 2011 года Sturla упоминает исправление утечки памяти, с тех пор я все еще испытывал утечки, используя оба репозитория, собственный Sturla Molden на GitHub (github.com/sturlamolden/sharedmem-numpy) и Крис Ли-Мессер включен Bitbucket (bitbucket.org/cleemesser/numpy-sharedmem).

если Ваш массив настолько велик, вы можете использовать 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))

обзоры ответы:

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