Объекты numpy.ndarray не собираются мусором

Пытаясь точно настроить некоторые утечки памяти в привязках Python для некоторых функций C/C++, я столкнулся с некоторым странным поведением, связанным со сборкой мусора из массивов Numpy.

Я создал пару упрощенных случаев, чтобы лучше объяснить поведение. Код был запущен с использованием memory_profiler, вывод которого следует сразу после него. Похоже, что сборка мусора Python не работает должным образом, когда дело доходит до массивов NumPy:

# File deallocate_ndarray.py
@profile
def ndarray_deletion():
    import numpy as np
    from gc import collect
    buf = 'abcdefghijklmnopqrstuvwxyz' * 10000
    arr = np.frombuffer(buf)
    del arr
    del buf
    collect()
    y = [i**2 for i in xrange(10000)]
    del y
    collect()

if __name__=='__main__':
    ndarray_deletion()

С помощью следующей команды я вызвал memory_profiler:

python -m memory_profiler deallocate_ndarray.py

Вот что я получил:

Filename: deallocate_ndarray.py
Line #    Mem usage    Increment   Line Contents
================================================
 5   10.379 MiB    0.000 MiB   @profile
 6                             def ndarray_deletion():
 7   17.746 MiB    7.367 MiB       import numpy as np
 8   17.746 MiB    0.000 MiB       from gc import collect
 9   17.996 MiB    0.250 MiB       buf = 'abcdefghijklmnopqrstuvwxyz' * 10000
10   18.004 MiB    0.008 MiB       arr = np.frombuffer(buf)
11   18.004 MiB    0.000 MiB       del arr
12   18.004 MiB    0.000 MiB       del buf
13   18.004 MiB    0.000 MiB       collect()
14   18.359 MiB    0.355 MiB       y = [i**2 for i in xrange(10000)]
15   18.359 MiB    0.000 MiB       del y
16   18.359 MiB    0.000 MiB       collect()

Я не понимаю, почему даже принудительные вызовы collect не уменьшают использование памяти программой, освобождая часть памяти. Более того, даже если массивы Numpy не ведут себя нормально из-за лежащих в их основе конструкций C, почему список (который является чистым Python) не собирает мусор?

Я знаю, что del не вызывает напрямую базовый метод __del__, но вы заметите, что все операторы del в коде фактически приводят к уменьшению счетчика ссылок соответствующих объектов до нуля (таким образом, AFAIK делает их подходящими для сборки мусора). Как правило, я ожидаю увидеть отрицательную запись в столбце приращения, когда объект подвергается сборке мусора. Кто-нибудь может пролить свет на то, что здесь происходит?

ПРИМЕЧАНИЕ. Этот тест выполнялся в OS X 10.10.4, Python 2.7.10 (conda), Numpy 1.9.2 (conda), Memory Profiler 0,33 (conda-binstar), psutil 2.2.1 (conda).


person prussian_metal    schedule 15.07.2015    source источник


Ответы (1)


Чтобы увидеть, как собирается мусор в памяти, мне пришлось увеличить размер buf на несколько порядков. Может быть, размер слишком мал для memory_profiler, чтобы обнаружить изменение (он запрашивает ОС, поэтому измерения не очень точны), или, может быть, он слишком мал для сборщика мусора Python, я не знаю.

Например, замена 10000 на 100000000 в множителе buf дает

Line #    Mem usage    Increment   Line Contents
================================================
21   10.289 MiB    0.000 MiB   @profile
22                             def ndarray_deletion():
23   17.309 MiB    7.020 MiB       import numpy as np
24   17.309 MiB    0.000 MiB       from gc import collect
25 2496.863 MiB 2479.555 MiB       buf = 'abcdefghijklmnopqrstuvwxyz' * 100000000
26 2496.867 MiB    0.004 MiB       arr = np.frombuffer(buf)
27 2496.867 MiB    0.000 MiB       del arr
28   17.312 MiB -2479.555 MiB       del buf
29   17.312 MiB    0.000 MiB       collect()
30   17.719 MiB    0.406 MiB       y = [i**2 for i in xrange(10000)]
31   17.719 MiB    0.000 MiB       del y
32   17.719 MiB    0.000 MiB       collect()
person Fabian Pedregosa    schedule 04.08.2015