Я играл с memory_profiler
в течение некоторого времени, и получила эти интересные, но запутанные результаты из небольшой программы ниже:память не освобождается после вызова функции в Python
import pandas as pd
import numpy as np
@profile
def f(p):
tmp = []
for _, frame in p.iteritems():
tmp.append([list(record) for record in frame.to_records(index=False)])
# initialize a list of pandas panels
lp = []
for j in xrange(50):
d = {}
for i in xrange(50):
df = pd.DataFrame(np.random.randn(200, 50))
d[i] = df
lp.append(pd.Panel(d))
# execution (iteration)
for panel in lp:
f(panel)
Тогда, если я использую mprof memory_profiler, чтобы проанализировать использование памяти во время выполнения, mprof run test.py
без каких-либо других параметров, я получаю это: .
Кажется, что память не выдается после каждого вызова функции f().
tmp
является только локальным списком и должен быть переназначен, и память перераспределяется каждый раз, когда вызывается f(). Очевидно, что на графике есть какое-то несоответствие. Я знаю, что у python есть свои собственные блоки управления памятью, а также есть бесплатный список для int и других типов, и gc.collect()
должен делать магию. Оказывается, явный gc.collect()
не работает. (Может быть, потому, что мы работаем с объектами панды, панелями и рамками? Я не знаю.)
Самая странная деталь, я не изменяю и не изменяю любую переменную в f()
. Все, что он делает, просто помещает копии списка в локальный список. Поэтому python не нужно делать копию чего-либо. Тогда почему и как это происходит?
=================
Некоторые другие наблюдения:
1) Если я называю f()
с f(panel.copy())
(последняя строка кода), передавая вместо исходной ссылки на объект, у меня есть совершенно другой результат использования памяти: . Является ли python умным, чтобы сказать, что это переданное значение является копией, чтобы он мог делать некоторые внутренние трюки, чтобы освободить память после каждого вызова функции?
2) Я думаю, это может быть из-за df.to_records()
. Хорошо, если я изменю его на frame.values
, я бы получил аналогичную плоскую кривую памяти, как показано на рисунке memory_profiling_results_2.png
, во время итерации (хотя мне нужно to_records()
, потому что он поддерживает колонку dtype, а .values
испортил dtypes). Но я посмотрел на реализацию frame.py на to_records()
. Я не понимаю, почему он будет держать память там, а .values
будет работать нормально.
Я запускаю программу на Windows с помощью python 2.7.8, memory_profiler 0.43 и psutil 5.0.1.
Спасибо @StephenRauch за это указание. Обновление об этом: это связано с кэшированием в пандах для данных. Когда '__getitem __()' вызывается для доступа к столбцам фрейма данных, каждый столбец будет храниться в '_item_cache'. И в этом случае это потому, что 'pd.to_records()' имеет представление списка, которое включает в себя 'self [c] для ...' в нем. На самом деле весь кадр данных кэшируется после вызова. –