Почему мой цикл требует больше памяти на каждой итерации?

Я пытаюсь уменьшить требования к памяти моего кода python 3. Прямо сейчас каждая итерация цикла for требует больше памяти, чем последняя.

Я написал небольшой фрагмент кода, который имеет то же поведение, что и мой проект:

import numpy as np from multiprocessing import Pool from itertools import repeat def simulation(steps, y): # the function that starts the parallel execution of f() pool = Pool(processes=8, maxtasksperchild=int(steps/8)) results = pool.starmap(f, zip(range(steps), repeat(y)), chunksize=int(steps/8)) pool.close() return results def f(steps, y): # steps is used as a counter. My code doesn't need it. a, b = np.random.random(2) return y*a, y*b def main(): steps = 2**20 # amount of times a random sample is taken y = np.ones(5) # dummy variable to show that the next iteration of the code depends on the previous one total_results = np.zeros((0,2)) for i in range(5): results = simulation(steps, y[i-1]) y[i] = results[0][0] total_results = np.vstack((total_results, results)) print(total_results, y) if __name__ == "__main__": main() 

Для каждой итерации цикла for потоки в симуляции () имеют использование памяти, равное суммарной памяти, используемой моим кодом.

Python клонирует всю мою среду каждый раз, когда выполняются параллельные процессы, включая переменные, не требуемые функцией f ()? Как я могу предотвратить это поведение?

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

2 Solutions collect form web for “Почему мой цикл требует больше памяти на каждой итерации?”

Хотя сценарий действительно использует довольно немного памяти даже с «меньшими» примерами значений, ответ на

Python клонирует всю мою среду каждый раз, когда выполняются параллельные процессы, включая переменные, не требуемые функцией f ()? Как я могу предотвратить это поведение?

заключается в том, что он делает так, чтобы клонировать среду с использованием нового процесса, но если доступна семантика copy-on-write , фактическая физическая память не должна копироваться до ее написания. Например, в этой системе

  % uname -a Linux mypc 4.2.0-27-generic #32-Ubuntu SMP Fri Jan 22 04:49:08 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux 

Кажется, что COW доступен и используется, но это может быть не так в других системах. В Windows это сильно отличается, поскольку новый интерпретатор Python выполняется из .exe вместо forking. Поскольку вы упоминаете использование htop , вы используете какой-то вкус UNIX или UNIX-подобной системы, и вы получаете семантику COW .

Для каждой итерации цикла for процессы в моделировании () имеют использование памяти, равное суммарной памяти, используемой моим кодом.

В порожденных процессах будут отображаться почти одинаковые значения RSS , но это может вводить в заблуждение, поскольку в основном они занимают одну и ту же фактическую физическую память, отображаемую для нескольких процессов, если записи не происходят. С Pool.map история немного сложнее, так как она «отбрасывает итерируемое число в несколько кусков, которые он представляет в пул процессов как отдельные задачи». Это отправление происходит через IPC и представленные данные будут скопированы. В вашем примере функции IPC и 2 ** 20 также доминируют в использовании ЦП. Замена отображения одним векторным умножением в simulation заняла время выполнения скрипта от 150 до 0,66 с на этом компьютере.

Мы можем наблюдать COW с (несколько) упрощенным примером, который выделяет большой массив и передает его процессу с порождением для обработки только для чтения:

 import numpy as np from multiprocessing import Process, Condition, Event from time import sleep import psutil def read_arr(arr, done, stop): with done: S = np.sum(arr) print(S) done.notify() while not stop.is_set(): sleep(1) def main(): # Create a large array print('Available before A (MiB):', psutil.virtual_memory().available / 1024 ** 2) input("Press Enter...") A = np.random.random(2**28) print('Available before Process (MiB):', psutil.virtual_memory().available / 1024 ** 2) input("Press Enter...") done = Condition() stop = Event() p = Process(target=read_arr, args=(A, done, stop)) with done: p.start() done.wait() print('Available with Process (MiB):', psutil.virtual_memory().available / 1024 ** 2) input("Press Enter...") stop.set() p.join() if __name__ == '__main__': main() 

Выход на этой машине:

  % python3 test.py Available before A (MiB): 7779.25 Press Enter... Available before Process (MiB): 5726.125 Press Enter... 134221579.355 Available with Process (MiB): 5720.79296875 Press Enter... 

Теперь, если мы заменим функцию read_arr на функцию, которая модифицирует массив:

 def mutate_arr(arr, done, stop): with done: arr[::4096] = 1 S = np.sum(arr) print(S) done.notify() while not stop.is_set(): sleep(1) 

результаты совершенно разные:

 Available before A (MiB): 7626.12109375 Press Enter... Available before Process (MiB): 5571.82421875 Press Enter... 134247509.654 Available with Process (MiB): 3518.453125 Press Enter... 

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

Возможно, вы должны знать разницу между thread и process в Operating System . см. это В чем разница между процессом и потоком .

В цикле for существуют processes , а не threads . Темы разделяют адресное пространство процесса, который его создал; процессы имеют собственное адресное пространство.

Вы можете распечатать идентификатор процесса, введите os.getpid() .

  • Использование Concurrent.Futures.ProcessPoolExecutor для запуска одновременных и независимых моделей ABAQUS
  • Многопроцессорность: используйте ** только ** физические ядра?
  • вычисление dask не выполняется параллельно
  • Keras + Tensorflow и многопроцессорность в Python
  • Многопроцессорность Python. Почему использование functools.partial медленнее, чем аргументы по умолчанию?
  • Многопроцессорность Python Pool.apply_async с общими переменными (значение)
  • Запуск scipy.integrate.ode в многопроцессорном пуле приводит к огромному результату
  • Многопроцессорные и Selenium Python
  • Python - лучший язык программирования в мире.