Як вивільнити пам'ять, використану в рамках даних панди?


111

У мене дійсно великий файл CSV, який я відкрив у пандах наступним чином ....

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

Після цього моє використання пам'яті збільшується на 2 Гб, що очікується, оскільки цей файл містить мільйони рядків. Моя проблема виникає, коли мені потрібно звільнити цю пам'ять. Я біг ....

del df

Однак використання моєї пам'яті не впало. Це неправильний підхід до звільнення пам'яті, використовуваної рамкою даних панди? Якщо це так, то який правильний шлях?


3
це правильно, збирач сміття може не випустити пам'ять відразу, ви також можете імпортувати gcмодуль і зателефонувати, gc.collect()але він може не відновити пам'ять
EdChum

del dfне називається безпосередньо після створення df права? Я думаю, що у точці, яку ви видаляєте, є посилання на df. Тож його не буде видалено, замість цього воно видалить ім'я.
Марлон Абейкон

4
Від того, чи повернута пам’ять збирачем сміття чи ні, насправді повертається ОС, це залежить від реалізації; Єдина гарантія, яку дає сміттєзбірник, - це те, що регенерована пам'ять може використовуватися поточним процесом Python для інших речей, а не для запиту або навіть більше пам'яті в ОС.
чепнер

Я дзвоню del df відразу після створення. Інших посилань на df я не додав. Все, що я робив, це відкрити ipython і запустити ці три рядки коду. Якщо я запускаю той самий код на якомусь іншому об'єкті, який займає багато пам'яті, наприклад скажімо, масивний масив. del nparray прекрасно працює
b10hazard

@ b10hazard: А як щодо чогось подібного df = ''в кінці коду? Здається очищення оперативної пам’яті, що використовується фреймом даних.
джибунет

Відповіді:


119

Зменшити використання пам'яті в Python важко, оскільки Python насправді не повертає пам'ять до операційної системи . Якщо ви видаляєте об'єкти, пам'ять доступна для нових об’єктів Python, але не free()повертається до системи ( див. Це питання ).

Якщо ви дотримуєтеся числових масивів, вони звільняються, але об'єкти в коробці - ні.

>>> 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, щоб ви не створювали копії.

Ще один розповсюджений gotcha - це копія раніше створених фреймів даних в 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

Значення з типом об'єкта є в коробці, що означає, що нумерований масив просто містить вказівник, і ви маєте повний об’єкт Python на купі для кожного значення у вашому фреймі даних. Сюди входять рядки.

Хоча numpy підтримує рядки фіксованого розміру в масивах, панди не мають ( це викликає плутанину користувачів ). Це може суттєво змінитись:

>>> 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

З панд 0.17.1 ви також df.info(memory_usage='deep')можете бачити використання пам'яті, включаючи об'єкти.


2
Це має бути позначено "Прийнятий відповідь". Це коротко, але чітко пояснює, як python утримує пам’ять, навіть коли він насправді не потребує цього. Поради щодо збереження пам’яті - всі розумні та корисні. В якості ще однієї поради я б просто додав "багатопроцесорний" (як пояснено у відповіді @ Ami.
pedram bashiri

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, панди, збирач сміття.


1
@ b10hazard Навіть не маючи панд, я ніколи не розумів, як пам'ять Python працює на практиці. Ця сира техніка - єдине, на що я покладаюся.
Амі Таворі

9
Працює дуже добре. Однак в середовищі ipython (наприклад, ноутбук з юпітером) я виявив, що вам потрібно закрити () і .join () або .terminate () пул, щоб позбутися від нерегулярного процесу. Найпростіший спосіб зробити це з моменту Python 3.3 - використовувати протокол управління контекстом: для with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])цього потрібно закрити пул.
Цертрін

2
Це добре, просто не забудьте завершити і приєднатися до пулу після того, як буде виконано завдання.
Андрій Нікішаєв

1
Прочитавши кілька разів про те, як повернути пам'ять з об’єкта python, це, здається, є найкращим способом зробити це. Створіть процес, і коли цей процес буде вбито, ОС звільняє пам'ять.
Муаммар

1
Можливо, це допоможе комусь під час створення пулу спробувати використовувати maxtasksperchild = 1 для того, щоб звільнити процес і породити новий після закінчення роботи.
giwiro

22

Це вирішує проблему звільнення для мене пам’яті !!!

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

кадр даних буде явно встановлений на нуль


1
Чому фрейми даних додані в підсписок [[df_1, df_2]]? Якась конкретна причина? Будь ласка, поясніть.
goks

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/isissue/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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.