Подтвердить что ты не робот

Общая память в многопроцессорной обработке

У меня есть три больших списка. Первый содержит битрейты (модуль bitarray 0.8.0), а два других содержат массивы целых чисел.

l1=[bitarray 1, bitarray 2, ... ,bitarray n]
l2=[array 1, array 2, ... , array n]
l3=[array 1, array 2, ... , array n]

Эти структуры данных занимают довольно много ОЗУ (~ 16 ГБ).

Если я запустил 12 подпроцессов, используя:

multiprocessing.Process(target=someFunction, args=(l1,l2,l3))

Означает ли это, что l1, l2 и l3 будут скопированы для каждого подпроцесса или будут ли подпроцессы разделять эти списки? Или, чтобы быть более прямым, я буду использовать 16 ГБ или 192 ГБ ОЗУ?

someFunction будет считывать некоторые значения из этих списков и затем выполняет некоторые вычисления на основе прочитанных значений. Результаты будут возвращены родительскому процессу. Списки l1, l2 и l3 не будут изменены некоторой функцией.

Поэтому я бы предположил, что подпроцессы не нужны и не будут копировать эти огромные списки, но вместо этого просто передадут их родителям. Это означает, что в программе потребуется 16 ГБ оперативной памяти (независимо от того, сколько подпроцессов я запускаю) из-за подхода copy-on-write под linux? Я исправляю или мне не хватает чего-то, что приведет к копированию списков?

ИЗМЕНИТЬ: Я все еще смущен, прочитав немного больше по этому вопросу. С одной стороны, Linux использует copy-on-write, что должно означать, что данные не копируются. С другой стороны, доступ к объекту изменит значение ref-count (я все еще не уверен, почему и что это значит). Тем не менее, будет ли скопирован весь объект?

Например, если я определяю someFunction следующим образом:

def someFunction(list1, list2, list3):
    i=random.randint(0,99999)
    print list1[i], list2[i], list3[i]

Будет ли использование этой функции означать, что l1, l2 и l3 будут полностью скопированы для каждого подпроцесса?

Есть ли способ проверить это?

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

Подсчет ссылок для l1, l2 и l3 фактически не используется в моей программе. Это связано с тем, что l1, l2 и l3 будут сохраняться в памяти (без изменений), пока не завершится родительский процесс. Нет необходимости освобождать память, используемую этими списками, до тех пор. На самом деле я точно знаю, что счетчик ссылок останется выше 0 (для этих списков и каждого объекта в этих списках) до выхода программы.

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

EDIT3 Еще одно примечание. Подпроцессам не нужно изменять l1, l2 и l3 или любые объекты в этих списках. Подпроцессы должны иметь возможность ссылаться на некоторые из этих объектов, не вызывая копирование памяти для каждого подпроцесса.

4b9b3361

Ответ 1

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

  • Многопоточность
  • Общая память

Многопоточность Python не подходит для задач, связанных с ЦП (из-за GIL), поэтому обычное решение в этом случае - продолжить multiprocessing. Однако с этим решением вам необходимо явно поделиться данными, используя multiprocessing.Value и multiprocessing.Array.

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

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

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

В вашем случае вам нужно обернуть l1, l2 и l3 в некотором смысле понятным с помощью multiprocessing (например, с помощью multiprocessing.Array), а затем передать их как параметры.
Также обратите внимание, что, как вы сказали, вам не нужен доступ на запись, вам следует передать lock=False при создании объектов, или весь доступ будет по-прежнему сериализован.

Ответ 2

Если вы хотите использовать функцию копирования на запись, а ваши данные статичны (не изменяются в дочерних процессах), вы должны сделать python не путайте с блоками памяти, где ваши данные лежат. Вы можете легко сделать это, используя структуры C или С++ (например, stl) в качестве контейнеров и предоставить свои собственные оболочки python, которые будут использовать указатели на память данных (или, возможно, копировать данные), когда объект уровня на основе python будет создан, если вообще, Все это можно сделать очень легко с почти простотой и синтаксисом python с cython.

# pseudo cython
cdef class FooContainer:
   cdef char * data
   def __cinit__(self, char * foo_value):
       self.data = malloc(1024, sizeof(char))
       memcpy(self.data, foo_value, min(1024, len(foo_value)))

   def get(self):
       return self.data

# python part
from foo import FooContainer

f = FooContainer("hello world")
pid = fork()
if not pid:
   f.get() # this call will read same memory page to where
           # parent process wrote 1024 chars of self.data
           # and cython will automatically create a new python string
           # object from it and return to caller

Вышеупомянутый псевдокод плохо написан. Не используйте его. Вместо self.data должен быть контейнер C или С++ в вашем случае.