2017-01-20 2 views
2

Я играл с 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 без каких-либо других параметров, я получаю это: memory_profiling_results_1.png.

Кажется, что память не выдается после каждого вызова функции f().

tmp является только локальным списком и должен быть переназначен, и память перераспределяется каждый раз, когда вызывается f(). Очевидно, что на графике есть какое-то несоответствие. Я знаю, что у python есть свои собственные блоки управления памятью, а также есть бесплатный список для int и других типов, и gc.collect() должен делать магию. Оказывается, явный gc.collect() не работает. (Может быть, потому, что мы работаем с объектами панды, панелями и рамками? Я не знаю.)

Самая странная деталь, я не изменяю и не изменяю любую переменную в f(). Все, что он делает, просто помещает копии списка в локальный список. Поэтому python не нужно делать копию чего-либо. Тогда почему и как это происходит?

=================

Некоторые другие наблюдения:

1) Если я называю f() с f(panel.copy()) (последняя строка кода), передавая вместо исходной ссылки на объект, у меня есть совершенно другой результат использования памяти: memory_profiling_results_2.png. Является ли 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.

+0

Спасибо @StephenRauch за это указание. Обновление об этом: это связано с кэшированием в пандах для данных. Когда '__getitem __()' вызывается для доступа к столбцам фрейма данных, каждый столбец будет храниться в '_item_cache'. И в этом случае это потому, что 'pd.to_records()' имеет представление списка, которое включает в себя 'self [c] для ...' в нем. На самом деле весь кадр данных кэшируется после вызова. –

ответ

0

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

import time 

# execution (iteration) 
start_time = time.time() 
for panel in lp: 
    f(panel) 
print(time.time() - start_time) 

print('-------------------------------------') 
start_time = time.time() 
for panel in lp: 
    f(panel) 
print(time.time() - start_time) 
+0

Спасибо за ответ первым! Да, я тоже это пробовал и знал о том, что это не утечка! Вы сказали, что это побочный эффект кеширования.Вы знаете, где происходит кеширование? Или как? Потому что я сделал несколько подобных экспериментов, и оказалось, что кеширование не происходит каждый раз. Например, если я использую '.values' вместо' to_records() 'Я не буду видеть увеличение памяти. Не могли бы вы указать мне, где кэширование реализовано в ядре pandas? Или просто объясните мне, когда кэш придет на наш путь? Любой способ работать вокруг? Использовать '.copy()'? –

+0

Причина, по которой мне нужно, чтобы это было «исправлено», состоит в том, что если у меня есть более длинный список, а 'lp' теперь состоит из 500 объектов, память будет слишком много. –

+0

С помощью отладчика (я использую «pycharm') вы можете войти в код' pandas', чтобы узнать, как он работает. –

Смежные вопросы