Как освободить память, используемую фреймом данных pandas?

111

У меня есть действительно большой файл csv, который я открыл в пандах следующим образом ....

import pandas
df = pandas.read_csv('large_txt_file.txt')

Как только я это сделаю, мое использование памяти увеличится на 2 ГБ, что и ожидается, поскольку этот файл содержит миллионы строк. Моя проблема возникает, когда мне нужно освободить эту память. Я сбежал ....

del df

Однако использование моей памяти не упало. Это неправильный подход к освобождению памяти, используемой фреймом данных pandas? Если да, то каков правильный способ?

b10опасность
источник
3
это правильно, сборщик мусора может не освободить память сразу, вы также можете импортировать gcмодуль и вызвать, gc.collect()но он может не восстановить память
EdChum
del dfне вызывается сразу после создания df, верно? Я думаю, что есть ссылки на df в момент удаления df. Таким образом, он не будет удален, вместо этого он удалит имя.
Марлон Абейкун
4
Возвращается ли память, освобожденная сборщиком мусора, обратно в ОС, зависит от реализации; Единственная гарантия, которую дает сборщик мусора, состоит в том, что освобожденная память может использоваться текущим процессом Python для других целей, вместо того, чтобы запрашивать или даже больше памяти у ОС.
chepner
Я вызываю del df сразу после создания. Других ссылок на df я не добавлял. Все, что я сделал, это открыл ipython и запустил эти три строчки кода. Если я запустил тот же код для другого объекта, который занимает много памяти, например, массив numpy. del nparray работает отлично
b10hazard
@ b10hazard: А что насчет того, что df = ''в конце вашего кода? Кажется, очищает RAM, используемую фреймом данных.
jibounet

Ответы:

120

Уменьшить использование памяти в Python сложно, потому что Python фактически не возвращает память операционной системе . Если вы удаляете объекты, память становится доступной для новых объектов Python, но не free()возвращается в систему ( см. Этот вопрос ).

Если вы придерживаетесь числовых массивов numpy, они освобождаются, а упакованные объекты - нет.

>>> import os, psutil, numpy as np
>>> def usage():
...     process = psutil.Process(os.getpid())
...     return process.get_memory_info()[0] / float(2 ** 20)
... 
>>> usage() # initial memory usage
27.5 

>>> arr = np.arange(10 ** 8) # create a large array without boxing
>>> usage()
790.46875
>>> del arr
>>> usage()
27.52734375 # numpy just free()'d the array

>>> arr = np.arange(10 ** 8, dtype='O') # create lots of objects
>>> usage()
3135.109375
>>> del arr
>>> usage()
2372.16796875  # numpy frees the array, but python keeps the heap big

Уменьшение количества фреймов данных

Python сохраняет нашу память на высоком уровне, но мы можем уменьшить общее количество создаваемых фреймов данных. При изменении фрейма данных предпочитайте inplace=True, чтобы вы не создавали копии.

Еще одна распространенная проблема - сохранение копий ранее созданных фреймов данных в ipython:

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({'foo': [1,2,3,4]})

In [3]: df + 1
Out[3]: 
   foo
0    2
1    3
2    4
3    5

In [4]: df + 2
Out[4]: 
   foo
0    3
1    4
2    5
3    6

In [5]: Out # Still has all our temporary DataFrame objects!
Out[5]: 
{3:    foo
 0    2
 1    3
 2    4
 3    5, 4:    foo
 0    3
 1    4
 2    5
 3    6}

Вы можете исправить это, набрав, %reset Outчтобы очистить историю. Кроме того, вы можете настроить, сколько истории хранится в ipython ipython --cache-size=5(по умолчанию 1000).

Уменьшение размера фрейма данных

По возможности избегайте использования типов объектов.

>>> df.dtypes
foo    float64 # 8 bytes per value
bar      int64 # 8 bytes per value
baz     object # at least 48 bytes per value, often more

Значения с объектом dtype помещены в рамку, что означает, что массив numpy просто содержит указатель, и у вас есть полный объект Python в куче для каждого значения в вашем фрейме данных. Сюда входят струны.

Хотя numpy поддерживает строки фиксированного размера в массивах, pandas - нет ( это вызвало путаницу у пользователей ). Это может иметь большое значение:

>>> import numpy as np
>>> arr = np.array(['foo', 'bar', 'baz'])
>>> arr.dtype
dtype('S3')
>>> arr.nbytes
9

>>> import sys; import pandas as pd
>>> s = pd.Series(['foo', 'bar', 'baz'])
dtype('O')
>>> sum(sys.getsizeof(x) for x in s)
120

Возможно, вы захотите избежать использования строковых столбцов или найти способ представления строковых данных в виде чисел.

Если у вас есть фрейм данных, который содержит много повторяющихся значений (NaN очень распространено), вы можете использовать разреженную структуру данных, чтобы уменьшить использование памяти:

>>> df1.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo    float64
dtypes: float64(1)
memory usage: 605.5 MB

>>> df1.shape
(39681584, 1)

>>> df1.foo.isnull().sum() * 100. / len(df1)
20.628483479893344 # so 20% of values are NaN

>>> df1.to_sparse().info()
<class 'pandas.sparse.frame.SparseDataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo    float64
dtypes: float64(1)
memory usage: 543.0 MB

Просмотр использования памяти

Вы можете просмотреть использование памяти ( документы ):

>>> df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 14 columns):
...
dtypes: datetime64[ns](1), float64(8), int64(1), object(4)
memory usage: 4.4+ GB

Начиная с pandas 0.17.1, вы также df.info(memory_usage='deep')можете видеть использование памяти, включая объекты.

Уилфред Хьюз
источник
2
Это должно быть помечено как «принятый ответ». В нем кратко, но ясно объясняется, как python сохраняет память, даже когда она действительно не нужна. Советы по экономии памяти все разумные и полезные. В качестве еще одного совета я бы просто добавил использование «многопроцессорности» (как объяснено в ответе
@Ami
46

Как отмечено в комментариях, есть несколько вещей, которые можно попробовать: gc.collect(@EdChum) может, например, очистить материал. По крайней мере, по моему опыту, эти вещи иногда работают, а часто - нет.

Однако есть одна вещь, которая работает всегда, потому что это делается на уровне ОС, а не языка.

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

def huge_intermediate_calc(something):
    ...
    huge_df = pd.DataFrame(...)
    ...
    return some_aggregate

Тогда, если вы сделаете что-то вроде

import multiprocessing

result = multiprocessing.Pool(1).map(huge_intermediate_calc, [something_])[0]

Затем функция выполняется в другом процессе . По завершении этого процесса ОС забирает все использованные ресурсы. На самом деле Python, pandas, сборщик мусора, ничего не могут сделать, чтобы остановить это.

Ами Тавори
источник
1
@ b10hazard Даже без панд я никогда полностью не понимал, как память Python работает на практике. Эта грубая техника - единственное, на что я полагаюсь.
Ами Тавори
9
Работает действительно хорошо. Однако в среде ipython (например, jupyter notebook) я обнаружил, что вам нужно .close () и .join () или .terminate () пул, чтобы избавиться от порожденного процесса. Самый простой способ сделать это, начиная с Python 3.3, - это использовать протокол управления контекстом: он with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])требует закрытия пула после завершения.
Zertrin
2
Это работает хорошо, только не забудьте завершить работу и присоединиться к пулу после выполнения задачи.
Андрей Никишаев
1
Прочитав несколько раз о том, как вернуть память из объекта python, кажется, что это лучший способ сделать это. Создайте процесс, и когда этот процесс будет убит, ОС освободит память.
muammar
1
Может быть, это кому-то поможет, при создании пула попробуйте использовать maxtasksperchild = 1, чтобы освободить процесс и создать новый после завершения работы.
giwiro
22

Это решает для меня проблему освобождения памяти !!!

del [[df_1,df_2]]
gc.collect()
df_1=pd.DataFrame()
df_2=pd.DataFrame()

фрейм данных будет явно установлен на null

Hardi
источник
1
Почему фреймы данных добавлены в подсписок [[df_1, df_2]]? Какая-то конкретная причина? Пожалуйста, объясни.
goks 09
5
Почему бы вам просто не использовать последние два утверждения? Не думаю, что вам нужны первые два утверждения.
spacedustpi
3

del dfне будет удален, если dfна момент удаления есть ссылка на . Поэтому вам нужно удалить все ссылки на него, del dfчтобы освободить память.

Поэтому все экземпляры, привязанные к df, должны быть удалены, чтобы запустить сборку мусора.

Используйте objgragh, чтобы проверить, кто удерживает объекты.

Марлон Абейкун
источник
ссылка указывает на объект objgraph ( mg.pov.lt/objgraph ), это опечатка в вашем ответе, если нет объекта objgragh
SatZ
1

Кажется, есть проблема с glibc, которая влияет на выделение памяти в Pandas: https://github.com/pandas-dev/pandas/issues/2659

Обезьяна патч подробно по этому вопросу решил проблему для меня:

# monkeypatches.py

# Solving memory leak problem in pandas
# https://github.com/pandas-dev/pandas/issues/2659#issuecomment-12021083
import pandas as pd
from ctypes import cdll, CDLL
try:
    cdll.LoadLibrary("libc.so.6")
    libc = CDLL("libc.so.6")
    libc.malloc_trim(0)
except (OSError, AttributeError):
    libc = None

__old_del = getattr(pd.DataFrame, '__del__', None)

def __new_del(self):
    if __old_del:
        __old_del(self)
    libc.malloc_trim(0)

if libc:
    print('Applying monkeypatch for pd.DataFrame.__del__', file=sys.stderr)
    pd.DataFrame.__del__ = __new_del
else:
    print('Skipping monkeypatch for pd.DataFrame.__del__: libc or malloc_trim() not found', file=sys.stderr)
MarkNS
источник