Come rilascio la memoria utilizzata da un dataframe Panda?


111

Ho un file csv molto grande che ho aperto in Panda come segue ...

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

Dopo aver eseguito questa operazione, l'utilizzo della memoria aumenta di 2 GB, il che è previsto perché questo file contiene milioni di righe. Il mio problema nasce quando devo liberare questa memoria. Ho corso ....

del df

Tuttavia, l'utilizzo della memoria non è diminuito. È questo l'approccio sbagliato per liberare la memoria utilizzata da un frame di dati Panda? Se lo è, qual è il modo corretto?


3
è corretto, il garbage collector potrebbe non rilasciare immediatamente la memoria, puoi anche importare il gcmodulo e chiamare gc.collect()ma potrebbe non recuperare la memoria
EdChum

del dfnon viene chiamato direttamente dopo la creazione di df giusto? Penso che ci siano riferimenti al df nel punto in cui elimini il df. Quindi non verrà cancellato invece cancella il nome.
Marlon Abeykoon

4
Il fatto che la memoria recuperata dal Garbage Collector venga effettivamente restituita o meno al sistema operativo dipende dall'implementazione; l'unica garanzia offerta dal garbage collector è che la memoria recuperata può essere utilizzata dall'attuale processo Python per altre cose invece di chiedere o anche più memoria dal sistema operativo.
chepner

Chiamo del df subito dopo la creazione. Non ho aggiunto altri riferimenti a df. Tutto quello che ho fatto è stato aprire ipython ed eseguire quelle tre righe di codice. Se eseguo lo stesso codice su un altro oggetto che richiede molta memoria, ad esempio un array numpy. del nparray funziona perfettamente
b10hazard

@ b10hazard: che ne dici di qualcosa come df = ''alla fine del codice? Sembra cancellare la RAM utilizzata dal dataframe.
jibounet

Risposte:


120

Ridurre l'utilizzo della memoria in Python è difficile, perché Python non rilascia effettivamente memoria al sistema operativo . Se elimini oggetti, la memoria è disponibile per i nuovi oggetti Python, ma non free()torna al sistema ( vedi questa domanda ).

Se ti attieni agli array numerici numpy, questi vengono liberati, ma gli oggetti in box non lo sono.

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

Riduzione del numero di frame di dati

Python mantiene la nostra memoria al massimo livello, ma possiamo ridurre il numero totale di frame di dati che creiamo. Quando modifichi il tuo dataframe, preferisci inplace=True, in modo da non creare copie.

Un altro trucco comune è mantenere le copie di dataframe creati in precedenza in 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}

Puoi risolvere questo problema digitando %reset Outper cancellare la cronologia. In alternativa, puoi regolare la quantità di cronologia che ipython conserva ipython --cache-size=5(il valore predefinito è 1000).

Riduzione delle dimensioni del dataframe

Ove possibile, evitare di utilizzare dtypes oggetto.

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

I valori con un oggetto dtype sono boxed, il che significa che l'array numpy contiene solo un puntatore e hai un oggetto Python completo sull'heap per ogni valore nel tuo dataframe. Ciò include le stringhe.

Mentre numpy supporta stringhe di dimensioni fisse negli array, panda no ( è causa di confusione dell'utente ). Questo può fare una differenza significativa:

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

Potresti voler evitare di usare colonne stringa o trovare un modo per rappresentare i dati stringa come numeri.

Se si dispone di un dataframe che contiene molti valori ripetuti (NaN è molto comune), è possibile utilizzare una struttura dati sparsa per ridurre l'utilizzo della memoria:

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

Visualizzazione dell'utilizzo della memoria

È possibile visualizzare l'utilizzo della memoria ( documenti ):

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

A partire da panda 0.17.1, puoi anche df.info(memory_usage='deep')vedere l'utilizzo della memoria inclusi gli oggetti.


2
Questo deve essere contrassegnato come "Risposta accettata". Spiega brevemente ma chiaramente come python mantiene la memoria anche quando non ne ha davvero bisogno. I suggerimenti per salvare la memoria sono tutti sensati e utili. Come altro suggerimento, aggiungerei semplicemente l'uso di "multiprocessing" (come spiegato nella risposta di @ Ami.
pedram bashiri,

46

Come notato nei commenti, ci sono alcune cose da provare: gc.collect(@EdChum) potrebbe cancellare cose, per esempio. Almeno dalla mia esperienza, queste cose a volte funzionano e spesso no.

C'è una cosa che funziona sempre, tuttavia, perché viene eseguita a livello di sistema operativo, non di lingua.

Supponiamo di avere una funzione che crea un DataFrame enorme intermedio e restituisce un risultato più piccolo (che potrebbe anche essere un DataFrame):

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

Quindi se fai qualcosa di simile

import multiprocessing

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

Quindi la funzione viene eseguita in un processo diverso . Al termine del processo, il sistema operativo riprende tutte le risorse utilizzate. Non c'è davvero niente che Python, i panda, il garbage collector, possano fare per fermarlo.


1
@ b10hazard Anche senza i panda, non ho mai capito appieno come funziona in pratica la memoria Python. Questa tecnica rozza è l'unica cosa su cui faccio affidamento.
Ami Tavory

9
Funziona davvero bene. Tuttavia in un ambiente ipython (come jupyter notebook) ho scoperto che è necessario .close () e .join () o .terminate () il pool per sbarazzarsi del processo generato. Il modo più semplice per farlo a partire da Python 3.3 è usare il protocollo di gestione del contesto: with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])che richiede di chiudere il pool una volta fatto.
Zertrin

2
Funziona bene, ma non dimenticare di terminare e unirti al pool dopo che l'attività è stata completata.
Andrey Nikishaev

1
Dopo aver letto più volte su come rivendicare la memoria da un oggetto Python, questo sembra essere il modo migliore per farlo. Crea un processo e quando quel processo viene terminato, il sistema operativo rilascia la memoria.
muammar

1
Forse aiuta qualcuno, quando si crea il pool, provare a utilizzare maxtasksperchild = 1 per rilasciare il processo e generarne uno nuovo al termine del lavoro.
giwiro

22

Questo risolve il problema di liberare la memoria per me !!!

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

il data-frame verrà impostato esplicitamente su null


1
Perché i dataframe sono stati aggiunti nel sottoelenco [[df_1, df_2]]? Qualche motivo specifico? Spiega per favore.
goks

5
Perché non usi solo le ultime due affermazioni? Non credo che tu abbia bisogno delle prime due affermazioni.
spacedustpi

3

del dfnon sarà cancellato se ci sono riferimenti al dfmomento della cancellazione. Quindi è necessario eliminare tutti i riferimenti ad esso con del dfper liberare la memoria.

Quindi tutte le istanze associate a df dovrebbero essere eliminate per attivare la garbage collection.

Usa objgragh per controllare quale sta trattenendo gli oggetti.


il link punta a objgraph ( mg.pov.lt/objgraph ), è un errore di battitura nella tua risposta a meno che non ci sia un objgragh
SatZ

1

Sembra che ci sia un problema con glibc che influisce sull'allocazione della memoria in Pandas: https://github.com/pandas-dev/pandas/issues/2659

La patch di scimmia dettagliata su questo problema ha risolto il problema per me:

# 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.