python: совместное использование огромных словарей с использованием многопроцессорности

Я обрабатываю очень большие объемы данных, хранящиеся в словаре, используя многопроцессорность. По сути, все, что я делаю, это загружаю некоторые подписи, хранящиеся в словаре, строю из него общий объект dict (получая объект 'proxy', возвращаемый Manager.dict ()), и передаю этот прокси в качестве аргумента функции, которая имеет для выполнения в многопроцессорном режиме.

Просто для ясности:

signatures = dict()
load_signatures(signatures)
[...]
manager = Manager()
signaturesProxy = manager.dict(signatures)
[...]
result = pool.map ( myfunction , [ signaturesProxy ]*NUM_CORES )

Теперь все работает отлично, если подписей меньше 2 миллионов записей или около того. В любом случае, мне нужно обработать словарь с 5,8 млн ключей (при обработке подписей в двоичном формате создается файл размером 4,8 ГБ). В этом случае процесс умирает во время создания прокси-объекта:

Traceback (most recent call last):
  File "matrix.py", line 617, in <module>
signaturesProxy = manager.dict(signatures)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 634, in temp
token, exp = self._create(typeid, *args, **kwds)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 534, in _create
id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 79, in dispatch
raise convert_to_error(kind, result)
multiprocessing.managers.RemoteError: 
---------------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 173, in handle_request
    request = c.recv()
EOFError
---------------------------------------------------------------------------

Я знаю, что структура данных огромна, но я работаю на машине, оснащенной 32 ГБ ОЗУ, и, работая наверху, я вижу, что процесс после загрузки подписей занимает 7 ГБ ОЗУ. Затем он начинает создание прокси-объекта, и использование ОЗУ увеличивается до ~ 17 ГБ, но никогда не приближается к 32. На этом этапе использование ОЗУ начинает быстро уменьшаться, и процесс завершается с указанной выше ошибкой. Так что я думаю, это не из-за ошибки нехватки памяти ...

Есть идея или предложение?

Спасибо,

Давид


person Davide C    schedule 26.12.2010    source источник


Ответы (4)


Если словари доступны только для чтения, вам не нужны прокси-объекты в большинстве операционных систем.

Просто загрузите словари перед запуском воркеров и поместите их в место, где они будут доступны; самое простое место - глобально к модулю. Они будут доступны для чтения рабочими.

from multiprocessing import Pool

buf = ""

def f(x):
    buf.find("x")
    return 0

if __name__ == '__main__':
    buf = "a" * 1024 * 1024 * 1024
    pool = Pool(processes=1)
    result = pool.apply_async(f, [10])
    print result.get(timeout=5)

При этом используется только 1 ГБ памяти вместе, а не 1 ГБ для каждого процесса, потому что любая современная ОС будет создавать тень копирования при записи данных, созданных до вилки. Просто помните, что изменения данных не будут видны другим рабочим, и память, конечно же, будет выделена для любых данных, которые вы изменяете.

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

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

person Glenn Maynard    schedule 26.12.2010
comment
Работает ли это с Windows 7 (которая определенно является современной ОС?) - person Seun Osewa; 09.04.2012
comment
@Seun: Я не знаю; попробуйте проверить это. Я сомневаюсь, что его модель процесса более современная, чем предыдущие версии; Windows всегда была в темноте по этому поводу. - person Glenn Maynard; 10.04.2012
comment
Я не думаю, что многопроцессорность использует копирование при записи. По моему опыту, данные будут дублироваться в каждом подпроцессе, даже если он доступен только для чтения. Эта поза, кажется, подтверждает следующее: stackoverflow.com/q/659865/5475 - person ibz; 24.04.2013
comment
Проголосовал против вашего ответа, но проголосовал за ваш комментарий (с чем я согласен!). :) - person ibz; 24.04.2013
comment
@ibz Документы Python (цитируемые в одном из ответов на этот вопрос) не согласны с вами и согласны с Гленном. См. docs.python.org/dev/library/ в явной форме. передавать ресурсы дочерним процессам - person Wlerin; 22.03.2017

Почему бы вам не попробовать это с базой данных? Базы данных не ограничены адресуемой / физической памятью и безопасны для многопоточного / процессного использования.

person Community    schedule 26.12.2010

В интересах экономии времени и отсутствия необходимости отлаживать проблемы на уровне системы, возможно, вы могли бы разделить свой 5,8-миллионный словарь записей на три набора по ~ 2 миллиона в каждом и запустить задание 3 раза.

person Fragsworth    schedule 26.12.2010
comment
Я мог бы, но это не оптимальное решение, так как в любом случае мне пришлось бы реконструировать весь словарь и использовать его для других операций. - person Davide C; 26.12.2010
comment
Тогда похоже, что ваша задача подходит для Hadoop / MapReduce ... Может, вам стоит это проверить. - person Fragsworth; 26.12.2010

Я думаю, что проблема, с которой вы столкнулись, заключалась в том, что размер таблицы или хеш-таблицы изменялся по мере роста. Изначально у dict есть заданное количество доступных сегментов. Я не уверен насчет Python, но я знаю, что Perl начинается с 8, а затем, когда ведра заполнены, хэш воссоздается еще на 8 (например, 8, 16, 32, ...).

Ковш - это место посадки для алгоритма хеширования. 8 слотов не означают 8 записей, это 8 ячеек памяти. Когда добавляется новый элемент, для этого ключа создается хэш, который затем сохраняется в этом сегменте.

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

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

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

примечание: я все еще новичок в Python, я знаю, что в Perl вы можете увидеть статистику хэша, выполнив print% HASHNAME, он покажет ваше распределение использования корзины. Помогает определить количество столкновений - на случай, если вам нужно заранее выделить сегменты. Можно ли это сделать и на Python?

Богатый

person Rich    schedule 21.02.2012