Многопроцессорность: совместное использование большого объекта только для чтения между процессами?

Создаются ли дочерние процессы через multiprocessing общие объекты, созданные ранее в программе?

У меня есть следующая настройка:

do_some_processing(filename):
    for line in file(filename):
        if line.split(',')[0] in big_lookup_object:
            # something here

if __name__ == '__main__':
    big_lookup_object = marshal.load('file.bin')
    pool = Pool(processes=4)
    print pool.map(do_some_processing, glob.glob('*.data'))

Я загружаю в память какой-то большой объект, а затем создаю пул работников, которым нужно использовать этот большой объект. Большой объект доступен только для чтения, мне не нужно передавать его между процессами.

Мой вопрос: большой объект загружен в разделяемую память, как это было бы, если бы я породил процесс в unix/c или каждый процесс загружал свою собственную копию большого объекта?

Обновление: уточнить - big_lookup_object - общий объект поиска. Мне не нужно разделить это и обработать его отдельно. Мне нужно сохранить одну копию. Работа, которую мне нужно разбить, - это чтение большого количества других больших файлов и поиск элементов в этих больших файлах против объекта поиска.

Дальнейшее обновление: база данных - прекрасное решение, memcached может быть лучшим решением, а файл на диске (полке или dbm) может быть еще лучше. В этом вопросе меня особенно интересовало решение в памяти. Для окончательного решения я буду использовать hadoop, но я хотел бы посмотреть, могу ли я иметь локальную версию в памяти.

Ответ 1

"Дочерние процессы порождаются через объекты общего доступа, созданные ранее в программе?"

Нет (python до 3.8) и Да в 3.8 (https://docs.python.org/3/library/multiprocessing.shared_memory.html#module-multiprocessing.shared_memory)

Процессы имеют независимое пространство памяти.

Решение 1

Чтобы лучше использовать большую структуру с большим количеством рабочих, сделайте это.

  1. Напишите каждого работника как "фильтр" & ndash; читает промежуточные результаты из стандартного ввода, работает, записывает промежуточные результаты в стандартный вывод.

  2. Соедините всех рабочих как pipeопровод:

    process1 <source | process2 | process3 | ... | processn >result
    

Каждый процесс читает, работает и пишет.

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


Решение 2

В некоторых случаях у вас есть более сложная структура & ndash; часто "разветвленная" структура. В этом случае у вас есть родитель с несколькими детьми.

  1. Родитель открывает исходные данные. Родитель разветвляется на нескольких детей.

  2. Родитель читает источник, передает части исходного кода каждому параллельно работающему дочернему элементу.

  3. Когда родитель достигнет конца, закройте pipeу. Ребенок получает конец файла и заканчивается нормально.

Дочерние части приятно писать, потому что каждый ребенок просто читает sys.stdin.

У родителя есть немного причудливой работы по порождению всех детей и правильному сохранению pipe, но это не так уж и плохо.

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

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


Решение 3

Общий поиск - это определение базы данных.

Решение 3А - загрузить базу данных. Пусть рабочие обрабатывают данные в базе данных.

Решение 3B - создайте очень простой сервер, используя werkzeug (или аналогичный), чтобы предоставлять приложения WSGI, которые отвечают на HTTP GET, чтобы работники могли запрашивать сервер.


Решение 4

Объект общей файловой системы. Unix OS предлагает объекты с общей памятью. Это просто файлы, которые сопоставлены с памятью, так что переключение ввода-вывода выполняется вместо обычных буферизованных операций чтения.

Вы можете сделать это из контекста Python несколькими способами

  1. Напишите программу запуска, которая (1) разбивает ваш оригинальный гигантский объект на более мелкие объекты, и (2) запускает рабочих, каждый из которых имеет меньший объект. Объекты меньшего размера могут быть выбраны из объектов Python, чтобы сэкономить немного времени на чтение файла.

  2. Напишите программу запуска, которая (1) читает ваш исходный гигантский объект и записывает файл со структурой страниц и байтовым кодом, используя операции seek, чтобы гарантировать, что отдельные разделы легко найти с помощью простых операций поиска. Это то, что делает движок базы данных & ndash; разбить данные на страницы, упростить поиск каждой страницы с помощью seek.

    Спавн-работники имеют доступ к этому большому файлу со структурой страниц. Каждый работник может искать соответствующие части и выполнять свою работу там.

Ответ 2

Выполняются ли дочерние процессы через многообъемные объекты общего доступа, созданные ранее в программе?

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

multiprocessing документация гласит:

Better to inherit than pickle/unpickle

В Windows многие типы из многопроцессорность должна быть сорвана так что дочерние процессы могут их использовать. Однако, как правило, следует избегать отправка общих объектов другим процессов с использованием труб или очередей. Вместо этого вы должны организовать программу так что процесс, который нуждается в доступе к общий ресурс, созданный в другом месте может наследовать его от предка процесс.

Explicitly pass resources to child processes

В Unix дочерний процесс может использовать общего ресурса, созданного в родительский процесс с использованием глобального ресурс. Однако лучше передать объект в качестве аргумента в конструктор для дочернего процесса.

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

Global variables

Имейте в виду, что если код запускается в дочерний процесс пытается получить доступ к глобальному переменная, то значение, которое она видит (если любой) может не совпадать с значением в родительском процессе в то время, когда Вызывается Process.start().

Пример

В Windows (один процессор):

#!/usr/bin/env python
import os, sys, time
from multiprocessing import Pool

x = 23000 # replace `23` due to small integers share representation
z = []    # integers are immutable, let try mutable object

def printx(y):
    global x
    if y == 3:
       x = -x
    z.append(y)
    print os.getpid(), x, id(x), z, id(z) 
    print y
    if len(sys.argv) == 2 and sys.argv[1] == "sleep":
       time.sleep(.1) # should make more apparant the effect

if __name__ == '__main__':
    pool = Pool(processes=4)
    pool.map(printx, (1,2,3,4))

С sleep:

$ python26 test_share.py sleep
2504 23000 11639492 [1] 10774408
1
2564 23000 11639492 [2] 10774408
2
2504 -23000 11639384 [1, 3] 10774408
3
4084 23000 11639492 [4] 10774408
4

Без sleep:

$ python26 test_share.py
1148 23000 11639492 [1] 10774408
1
1148 23000 11639492 [1, 2] 10774408
2
1148 -23000 11639324 [1, 2, 3] 10774408
3
1148 -23000 11639324 [1, 2, 3, 4] 10774408
4

Ответ 3

S.Lott является правильным. Ярлыки многопроцессорной обработки Python эффективно предоставляют вам отдельный дублированный фрагмент памяти.

В большинстве систем * nix использование низкоуровневого вызова os.fork(), фактически, даст вам копию на запись, что может быть тем, о чем вы думаете. AFAIK, теоретически, в наиболее упрощенных программах, вы можете читать эти данные без дублирования.

Однако в интерпретаторе Python все не так просто. Объектные данные и метаданные хранятся в одном сегменте памяти, поэтому даже если объект никогда не изменяется, что-то вроде счетчика ссылок для этого увеличиваемого объекта приведет к записи в память и, следовательно, к копии. Почти любая программа Python, которая выполняет больше, чем "print" hello ", будет увеличивать количество ссылок, поэтому вы, вероятно, никогда не поймете преимущества копирования-на-записи.

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

Ответ 4

Если вы работаете под Unix, они могут использовать один и тот же объект из-за как работает fork (то есть дочерние процессы имеют отдельный но он копирует-на-запись, поэтому он может быть разделен до тех пор, пока никто не изменяет его). Я попробовал следующее:

import multiprocessing

x = 23

def printx(y):
    print x, id(x)
    print y

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    pool.map(printx, (1,2,3,4))

и получил следующий вывод:

$ ./mtest.py
23 22995656
1
23 22995656
2
23 22995656
3
23 22995656
4

Конечно, это не доказывает, что копия не была сделана, но вы должны убедиться в том, что в вашей ситуации просмотрите вывод ps, чтобы узнать, сколько реальной памяти используется в каждом подпроцессе.

Ответ 5

Различные процессы имеют различное адресное пространство. Подобно запуску разных экземпляров интерпретатора. Это то, что для IPC (interprocess communication) для.

Для этой цели вы можете использовать либо очереди, либо каналы. Вы также можете использовать rpc over tcp, если вы хотите позже распределить процессы по сети.

http://docs.python.org/dev/library/multiprocessing.html#exchanging-objects-between-processes

Ответ 6

Не имеет прямого отношения к многопроцессорности как таковой, но из вашего примера, похоже, вы могли бы просто использовать модуль shelve или что-то как это. Действительно ли "big_lookup_object" действительно должен быть полностью в памяти?

Ответ 7

Нет, но вы можете загрузить свои данные как дочерний процесс и позволить ему делиться своими данными с другими дочерними процессами. Смотри ниже.

import time
import multiprocessing

def load_data( queue_load, n_processes )

    ... load data here into some_variable

    """
    Store multiple copies of the data into
    the data queue. There needs to be enough
    copies available for each process to access. 
    """

    for i in range(n_processes):
        queue_load.put(some_variable)


def work_with_data( queue_data, queue_load ):

    # Wait for load_data() to complete
    while queue_load.empty():
        time.sleep(1)

    some_variable = queue_load.get()

    """
    ! Tuples can also be used here
    if you have multiple data files
    you wish to keep seperate.  
    a,b = queue_load.get()
    """

    ... do some stuff, resulting in new_data

    # store it in the queue
    queue_data.put(new_data)


def start_multiprocess():

    n_processes = 5

    processes = []
    stored_data = []

    # Create two Queues
    queue_load = multiprocessing.Queue()
    queue_data = multiprocessing.Queue()

    for i in range(n_processes):

        if i == 0:
            # Your big data file will be loaded here...
            p = multiprocessing.Process(target = load_data,
            args=(queue_load, n_processes))

            processes.append(p)
            p.start()   

        # ... and then it will be used here with each process
        p = multiprocessing.Process(target = work_with_data,
        args=(queue_data, queue_load))

        processes.append(p)
        p.start()

    for i in range(n_processes)
        new_data = queue_data.get()
        stored_data.append(new_data)    

    for p in processes:
        p.join()
    print(processes)    

Ответ 8

Для платформы Linux/Unix/MacOS forkmap - это быстрое и грязное решение.