Способы освободить память обратно в ОС из Python?

У меня есть код, похожий на этот:

def memoryIntensiveFunction(x):
    largeTempVariable = Intermediate(x)
    processFunction(largeTempVariable,x)

Проблема в том, что переменная temp составляет около 500 МБ в моем тестовом примере, но это пространство не возвращается в ОС, когда memoryIntensiveFunction завершается. Я знаю это, потому что профилирование памяти с помощью инструмента guppy говорит, что largeTempVariable освобождено (т. е. внутри Python), но psutil показывает, что это не так. Я предполагаю, что вижу эффекты, описанные здесь. Проблема в том, что этот процесс идет долго (т.е. часы), memoryIntensiveFunction запускается в начале и больше никогда, поэтому мне неудобно таскать 500мб часами.

Одно решение я нашел здесь и здесь предлагается использовать отдельный процесс. Многопроцессорность требует собственных затрат, но в моем случае это того стоило. Однако для этого потребуется рефакторинг вызывающих memoryIntensiveFunction объектов, чтобы они получали x в качестве возвращаемого значения, а не видели его измененным на месте. Настоящим убийцей является то, что мой объект x не поддается выборке (он активно использует расширения boost python). Потребовалось бы много работы, чтобы сделать x пригодным для травления.

Есть варианты, которые я не рассматриваю?


person amos    schedule 03.07.2014    source источник


Ответы (1)


Это кажется достаточно любопытным, поэтому я попытался воспроизвести вашу проблему, и кажется, что простого «del» было достаточно. Для демонстрации вы можете запустить следующий код:

import itertools
import pdb

def test():
    a = "a"
    for _ in itertools.repeat(None, 30):
        a += a
    pdb.set_trace()
    del a
    pdb.set_trace()

test()

И в первой точке останова вы увидите, что он использует примерно 1 ГБ оперативной памяти (вам нужна запись python3.3):

 Private  +   Shared  =  RAM used       Program

  4.0 KiB +   9.0 KiB =  13.0 KiB       VisualGDB-DisownTTY-r1
  4.0 KiB +  15.0 KiB =  19.0 KiB       sharing-tests
  4.0 KiB +  19.5 KiB =  23.5 KiB       dhcpcd
  4.0 KiB +  31.5 KiB =  35.5 KiB       gdb
  4.0 KiB +  36.0 KiB =  40.0 KiB       vim [deleted]
  4.0 KiB +  38.0 KiB =  42.0 KiB       systemd-udevd
 40.0 KiB +  10.0 KiB =  50.0 KiB       init
 24.0 KiB + 135.0 KiB = 159.0 KiB       agetty (6)
 12.0 KiB + 150.0 KiB = 162.0 KiB       su (3)
 88.0 KiB + 103.0 KiB = 191.0 KiB       syslog-ng (2)
152.0 KiB +  55.0 KiB = 207.0 KiB       crond
172.0 KiB +  81.0 KiB = 253.0 KiB       python3.4
580.0 KiB + 220.5 KiB = 800.5 KiB       sshd (3)
768.0 KiB + 932.0 KiB =   1.7 MiB       bash (13)
  2.8 MiB + 118.0 KiB =   2.9 MiB       mongod
  7.4 MiB + 109.0 KiB =   7.5 MiB       tmux [deleted] (2)
  1.0 GiB +   1.2 MiB =   1.0 GiB       python3.3
---------------------------------
                          1.0 GiB
=================================

А затем во второй точке останова, после удаления переменной, память освобождается:

 Private  +   Shared  =  RAM used       Program

  4.0 KiB +   9.0 KiB =  13.0 KiB       VisualGDB-DisownTTY-r1
  4.0 KiB +  15.0 KiB =  19.0 KiB       sharing-tests
  4.0 KiB +  19.5 KiB =  23.5 KiB       dhcpcd
  4.0 KiB +  31.5 KiB =  35.5 KiB       gdb
  4.0 KiB +  36.0 KiB =  40.0 KiB       vim [deleted]
  4.0 KiB +  38.0 KiB =  42.0 KiB       systemd-udevd
 40.0 KiB +  10.0 KiB =  50.0 KiB       init
 24.0 KiB + 135.0 KiB = 159.0 KiB       agetty (6)
 12.0 KiB + 150.0 KiB = 162.0 KiB       su (3)
 88.0 KiB + 103.0 KiB = 191.0 KiB       syslog-ng (2)
152.0 KiB +  55.0 KiB = 207.0 KiB       crond
172.0 KiB +  81.0 KiB = 253.0 KiB       python3.4
584.0 KiB + 220.5 KiB = 804.5 KiB       sshd (3)
768.0 KiB + 928.0 KiB =   1.7 MiB       bash (13)
  2.8 MiB + 118.0 KiB =   2.9 MiB       mongod
  5.1 MiB +   1.2 MiB =   6.3 MiB       python3.3
  7.4 MiB + 109.0 KiB =   7.5 MiB       tmux [deleted] (2)
---------------------------------
                         20.3 MiB
=================================

Теперь, если мы удалим "del" из функции и установим точку останова сразу после test():

import itertools
import pdb

def test():
    a = "a"
    for _ in itertools.repeat(None, 30):
        a += a
    pdb.set_trace()

test()
pdb.set_trace()

Память действительно не будет освобождена до завершения:

 Private  +   Shared  =  RAM used       Program

  4.0 KiB +   9.0 KiB =  13.0 KiB       VisualGDB-DisownTTY-r1
  4.0 KiB +  15.0 KiB =  19.0 KiB       sharing-tests
  4.0 KiB +  19.5 KiB =  23.5 KiB       dhcpcd
  4.0 KiB +  31.5 KiB =  35.5 KiB       gdb
  4.0 KiB +  36.0 KiB =  40.0 KiB       vim [deleted]
  4.0 KiB +  38.0 KiB =  42.0 KiB       systemd-udevd
 40.0 KiB +  10.0 KiB =  50.0 KiB       init
 24.0 KiB + 135.0 KiB = 159.0 KiB       agetty (6)
 12.0 KiB + 150.0 KiB = 162.0 KiB       su (3)
160.0 KiB +  53.0 KiB = 213.0 KiB       crond
172.0 KiB +  81.0 KiB = 253.0 KiB       python3.4
628.0 KiB + 219.5 KiB = 847.5 KiB       sshd (3)
836.0 KiB + 152.0 KiB = 988.0 KiB       syslog-ng (2)
752.0 KiB + 957.0 KiB =   1.7 MiB       bash (13)
  2.8 MiB + 113.0 KiB =   2.9 MiB       mongod
  7.4 MiB + 108.0 KiB =   7.6 MiB       tmux [deleted] (2)
  1.0 GiB +   1.1 MiB =   1.0 GiB       python3.3
---------------------------------
                          1.0 GiB
=================================

Итак, мое предложение? Просто удалите присоску после того, как вы ее использовали, и она больше не нужна;)

person Tymoteusz Paul    schedule 04.07.2014
comment
Обратите внимание, что я использую Python 2.7, поэтому, возможно, в Python 3 есть улучшения оптимизации памяти. Кроме того, я смог получить простой тестовый пример, включающий создание [1.0]*10**7, и del смог освободить его, как наблюдалось, используя psutil, но мой объект largeTempVariable как-то нетривиально в своей структуре данных, что выход за рамки не освобождает ее обратно в ОС. - person amos; 04.07.2014
comment
@amos так что же это такое? В OP вы говорите, что это большая переменная - и это то, что я использовал для теста, и оно работает так же с 2.7. Но теперь вы, кажется, указываете, что это не переменная, а список? Так что же это такое? - person Tymoteusz Paul; 04.07.2014
comment
Это объект, содержащий словари, наборы, вложенные словари и наборы и т. д. Внизу это целые числа и строки... - person amos; 04.07.2014
comment
@amos, который все еще довольно расплывчатый, слишком расплывчатый, чтобы на самом деле можно было точно определить, что вызывает проблему с не освобождением памяти. Имея это в виду, я не могу помочь вам иначе, чем сказать: либо переделайте класс, чтобы сделать его проще, либо переделайте ваше приложение, чтобы оно стало более атомарным, чтобы вы могли разбить его на отдельные потоки. - person Tymoteusz Paul; 04.07.2014
comment
Это может зависеть от ОС. Я считаю, что Linux особенно склонен не восстанавливать неиспользуемую память, предполагая, что неиспользуемая память используется только на бумаге, то есть, поскольку это не горячая память, она будет заменена, что само по себе является адекватным решением. - person Andrew Gorcester; 04.07.2014