многопроцессорность: совместное использование большого объекта только для чтения между процессами? - PullRequest
92 голосов
/ 18 марта 2009

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

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

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, а файл на диске (shelve или dbm) - еще лучше. В этом вопросе меня особенно интересовало решение для памяти. Для окончательного решения я буду использовать hadoop, но я хотел посмотреть, могу ли я также иметь локальную версию в памяти.

Ответы [ 8 ]

46 голосов
/ 18 марта 2009

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

Нет.

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

Раствор 1

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

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

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

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

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

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


Решение 2

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

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

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

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

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

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

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

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


Раствор 3

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

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

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


Раствор 4

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

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

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

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

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

35 голосов
/ 19 марта 2009

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

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

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

Better to inherit than pickle/unpickle

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

Explicitly pass resources to child processes

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

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

Global variables

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

Пример * +1034 * В 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's 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

26 голосов
/ 18 марта 2009

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

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

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

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

6 голосов
/ 18 марта 2009

Если вы работаете в 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, чтобы увидеть, насколько реально памяти, которую использует каждый подпроцесс.

2 голосов
/ 18 марта 2009

Разные процессы имеют разное адресное пространство. Как запустить разные экземпляры переводчика. Вот для чего предназначен IPC (межпроцессное взаимодействие).

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

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

1 голос
/ 20 марта 2019

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

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)    
1 голос
/ 19 марта 2009

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

0 голосов
/ 08 декабря 2015

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...