Come posso liberare esplicitamente memoria in Python?


388

Ho scritto un programma Python che agisce su un file di input di grandi dimensioni per creare alcuni milioni di oggetti che rappresentano triangoli. L'algoritmo è:

  1. leggere un file di input
  2. elaborare il file e creare un elenco di triangoli, rappresentati dai loro vertici
  3. emette i vertici nel formato OFF: un elenco di vertici seguito da un elenco di triangoli. I triangoli sono rappresentati da indici nell'elenco dei vertici

Il requisito OFF di stampare l'elenco completo dei vertici prima di stampare i triangoli significa che devo tenere in memoria l'elenco dei triangoli prima di scrivere l'output su file. Nel frattempo ricevo errori di memoria a causa delle dimensioni delle liste.

Qual è il modo migliore per dire a Python che non ho più bisogno di alcuni dei dati e che possono essere liberati?


11
Perché non stampare i triangoli su un file intermedio e rileggerli di nuovo quando ne hai bisogno?
Alice Purcell,

2
Questa domanda potrebbe potenzialmente riguardare due cose abbastanza diverse. Questi errori provengono dallo stesso processo Python , nel qual caso ci preoccupiamo di liberare memoria nell'heap del processo Python o provengono da processi diversi sul sistema, nel qual caso ci preoccupiamo di liberare memoria nel sistema operativo?
Charles Duffy,

Risposte:


456

Secondo la documentazione ufficiale di Python , è possibile forzare Garbage Collector a liberare memoria senza riferimenti gc.collect(). Esempio:

import gc
gc.collect()

19
Le cose vengono comunque raccolte di frequente, tranne che in alcuni casi insoliti, quindi non credo che sarà di grande aiuto.
Lennart Regebro,

24
In generale, gc.collect () deve essere evitato. Il garbage collector sa come fare il suo lavoro. Detto questo, se l'OP si trova in una situazione in cui improvvisamente sta deallocando molti oggetti (come in milioni), gc.collect potrebbe rivelarsi utile.
Jason Baker,

165
In realtà, chiamare gc.collect()te stesso alla fine di un ciclo può aiutare a evitare di frammentare la memoria, il che a sua volta aiuta a mantenere alte le prestazioni. Ho visto questo fare una differenza significativa (~ 20% di runtime IIRC)
RobM

39
Sto usando Python 3.6. La chiamata gc.collect()dopo il caricamento di un frame di dati Panda da hdf5 (500k righe) ha ridotto l'utilizzo della memoria da 1,7 GB a 500 MB
John

15
Devo caricare ed elaborare diversi array intorpiditi da 25 GB in un sistema con memoria da 32 GB. L'uso del my_arrayseguito da gc.collect()dopo l'elaborazione dell'array è l'unico modo in cui la memoria viene effettivamente rilasciata e il mio processo sopravvive per caricare l'array successivo.
David,

113

Sfortunatamente (a seconda della versione e del rilascio di Python) alcuni tipi di oggetti utilizzano "elenchi gratuiti" che sono un'ottimizzazione locale ordinata ma possono causare frammentazione della memoria, in particolare rendendo sempre più memoria "assegnata" solo agli oggetti di un certo tipo e quindi non disponibile per il "fondo generale".

L'unico modo veramente affidabile per garantire che un uso ampio ma temporaneo della memoria restituisca tutte le risorse al sistema quando viene eseguito, è che tale utilizzo avvenga in un sottoprocesso, che termina il lavoro affamato di memoria. In tali condizioni, il sistema operativo farà il suo lavoro e riciclerà volentieri tutte le risorse che il sottoprocesso potrebbe aver inghiottito. Fortunatamente, il multiprocessingmodulo rende questo tipo di operazione (che era piuttosto una seccatura) non troppo male nelle versioni moderne di Python.

Nel tuo caso d'uso, sembra che il modo migliore per i sottoprocessi di accumulare alcuni risultati e tuttavia garantire che tali risultati siano disponibili per il processo principale sia utilizzare file semi-temporanei (con semi-temporanei intendo, NON il tipo di file che scompare automaticamente quando viene chiuso, solo i normali file che elimini esplicitamente quando hai finito con loro).


31
Mi piacerebbe vedere un banale esempio di questo.
Aaron Hall

3
Sul serio. Cosa ha detto @AaronHall.
Noob Saibot,

17
@AaronHall Esempio di Trivial ora disponibile , usando multiprocessing.Manageranziché i file per implementare lo stato condiviso.
user4815162342

48

La deldichiarazione potrebbe essere utile, ma IIRC non è garantito per liberare la memoria . I documenti sono qui ... e un motivo per cui non è stato rilasciato è qui .

Ho sentito persone su sistemi di tipo Linux e Unix creare un processo Python per fare un po 'di lavoro, ottenere risultati e poi ucciderlo.

Questo articolo contiene note sul Garbage Collector di Python, ma penso che la mancanza di controllo della memoria sia il rovescio della medaglia della memoria gestita


IronPython e Jython sarebbero un'altra opzione per evitare questo problema?
Esteban Küber,

@voyager: No, non lo farebbe. E nemmeno un'altra lingua, davvero. Il problema è che legge grandi quantità di dati in un elenco e che i dati sono troppo grandi per la memoria.
Lennart Regebro,

1
Probabilmente sarebbe peggio con IronPython o Jython. In quegli ambienti, non sei nemmeno garantito che la memoria verrà rilasciata se nient'altro contiene un riferimento.
Jason Baker,

@voyager, sì, perché la macchina virtuale Java cerca globalmente memoria libera. Per JVM, Jython non è niente di speciale. D'altra parte, la JVM ha una propria quota di inconvenienti, ad esempio è necessario dichiarare in anticipo quanto grande heap può usare.
contratto del Prof. Falken è stato violato

32

Python viene raccolto in modo inutile, quindi se riduci le dimensioni del tuo elenco, recupererà la memoria. Puoi anche usare l'istruzione "del" per eliminare completamente una variabile:

biglist = [blah,blah,blah]
#...
del biglist

18
Questo è e non è vero. Mentre la riduzione della dimensione dell'elenco consente di recuperare la memoria, non è garantito quando ciò accadrà.
user142350

3
No, ma di solito aiuterà. Tuttavia, quando capisco la domanda qui, il problema è che deve avere così tanti oggetti da esaurire la memoria prima di elaborarli tutti, se li legge in un elenco. È improbabile che l'eliminazione dell'elenco prima del completamento dell'elaborazione sia una soluzione utile. ;)
Lennart Regebro,

3
Una condizione di memoria insufficiente / memoria insufficiente non innescherebbe una "corsa di emergenza" del garbage collector?
Jeremy Friesner,

4
biglist = [] rilascerà memoria?
neouyghur,

3
sì, se la vecchia lista non fa riferimento a nient'altro.
Ned Batchelder,

22

Non è possibile liberare esplicitamente memoria. Quello che devi fare è assicurarti di non mantenere i riferimenti agli oggetti. Verranno quindi raccolti i rifiuti, liberando la memoria.

Nel tuo caso, quando hai bisogno di elenchi di grandi dimensioni, in genere è necessario riorganizzare il codice, utilizzando invece generatori / iteratori. In questo modo non è necessario avere gli elenchi di grandi dimensioni in memoria.

http://www.prasannatech.net/2009/07/introduction-python-generators.html


1
Se questo approccio è fattibile, probabilmente vale la pena farlo. Ma va notato che non è possibile eseguire l'accesso casuale sugli iteratori, il che può causare problemi.
Jason Baker,

Questo è vero e, se necessario, è probabile che l'accesso a grandi serie di dati in modo casuale richieda una sorta di database.
Lennart Regebro,

È possibile utilizzare facilmente un iteratore per estrarre un sottoinsieme casuale di un altro iteratore.
S.Lott

Vero, ma poi dovresti scorrere tutto per ottenere il sottoinsieme, che sarà molto lento.
Lennart Regebro,

21

(del può essere tuo amico, in quanto contrassegna gli oggetti come eliminabili quando non vi sono altri riferimenti ad essi. Ora, spesso l'interprete CPython conserva questa memoria per un uso successivo, quindi il tuo sistema operativo potrebbe non vedere la memoria "liberata".)

Forse non dovresti incontrare alcun problema di memoria in primo luogo utilizzando una struttura più compatta per i tuoi dati. Pertanto, gli elenchi di numeri sono molto meno efficienti in termini di memoria rispetto al formato utilizzato dal arraymodulo standard o dal modulo di terze parti numpy. Risparmierai memoria mettendo i tuoi vertici in un array NumPy 3xN e i tuoi triangoli in un array N-element.


Eh? La garbage collection di CPython è basata sul refounting; non è un contrassegno periodico (come per molte implementazioni JVM comuni), ma elimina immediatamente qualcosa nel momento in cui il suo conteggio di riferimento raggiunge lo zero. Solo i cicli (in cui i conteggi sarebbero pari a zero ma non sono dovuti a cicli nella struttura di riferimento) richiedono una manutenzione periodica. delnon fa nulla che riassegni un valore diverso a tutti i nomi che fanno riferimento a un oggetto.
Charles Duffy,

Vedo da dove vieni: aggiornerò la risposta di conseguenza. Capisco che l'interprete CPython funziona effettivamente in qualche modo intermedio: dellibera la memoria dal punto di vista di Python, ma generalmente non dal punto di vista della libreria di runtime o del sistema operativo. Riferimenti: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/... .
Eric O Lebigot,

Concordato sul contenuto dei tuoi collegamenti, ma supponendo che l'OP stia parlando di un errore che ottengono dallo stesso processo Python , la distinzione tra liberare memoria nell'heap locale del processo e nel sistema operativo non sembra essere rilevante ( poiché la liberazione nell'heap rende disponibile quello spazio per nuove allocazioni all'interno di quel processo Python). E per questo, delè ugualmente efficace con le uscite dal campo di applicazione, le riassegnazioni, ecc.
Charles Duffy,

11

Ho avuto un problema simile nella lettura di un grafico da un file. L'elaborazione includeva il calcolo di una matrice float 200 000x200 000 (una riga alla volta) che non rientrava nella memoria. Il tentativo di liberare la memoria tra i calcoli utilizzando gc.collect()risolto l'aspetto del problema relativo alla memoria, ma ha comportato problemi di prestazioni: non so perché ma anche se la quantità di memoria utilizzata è rimasta costante, ogni nuova chiamata agc.collect() impiegato più tempo di il precedente. Così rapidamente la raccolta dei rifiuti ha richiesto gran parte del tempo di calcolo.

Per risolvere sia i problemi di memoria che quelli relativi alle prestazioni, sono passato all'uso di un trucco multithreading che ho letto una volta da qualche parte (mi dispiace, non riesco più a trovare il relativo post). Prima stavo leggendo ogni riga del file in un grande forciclo, elaborandolo ed eseguendologc.collect() ogni tanto per liberare spazio di memoria. Ora chiamo una funzione che legge ed elabora un pezzo del file in un nuovo thread. Al termine del thread, la memoria viene automaticamente liberata senza lo strano problema di prestazioni.

Praticamente funziona così:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

1
Mi chiedo perché stai usando `//` `s invece di # in Python per i commenti.
JC Rocamonde,

Mi sono confuso tra le lingue. Grazie per l'osservazione, ho aggiornato la sintassi.
Retzod,

9

Altri hanno pubblicato alcuni modi in cui potresti essere in grado di "convincere" l'interprete Python a liberare la memoria (o altrimenti evitare di avere problemi di memoria). È probabile che dovresti provare prima le loro idee. Tuttavia, ritengo importante darti una risposta diretta alla tua domanda.

Non c'è davvero modo di dire direttamente a Python di liberare memoria. Il fatto è che se vuoi un livello di controllo così basso, dovrai scrivere un'estensione in C o C ++.

Detto questo, ci sono alcuni strumenti per aiutarti in questo:


3
gc.collect () e del gc.garbage [:] funzionano bene quando sto usando grandi quantità di memoria
Andrew Scott Evans

3

Se non ti interessa il riutilizzo dei vertici, potresti avere due file di output: uno per i vertici e uno per i triangoli. Quindi aggiungi il file del triangolo al file del vertice quando hai finito.


1
Immagino di poter mantenere solo i vertici in memoria e stampare i triangoli su un file, quindi stampare i vertici solo alla fine. Tuttavia, l'atto di scrivere i triangoli in un file è un enorme consumo di prestazioni. Esiste un modo per velocizzare che fino?
Nathan Fellman,
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.