¿Cómo puedo liberar explícitamente memoria en Python?


Escribí un programa Python que actúa sobre un archivo de entrada grande para crear unos pocos millones de objetos que representan triángulos. El algoritmo es:

  1. leer un archivo de entrada
  2. procesa el archivo y crea una lista de triángulos, representados por sus vértices
  3. muestra los vértices en formato OFF: una lista de vértices seguida de una lista de triángulos. Los triángulos están representados por índices en la lista de vértices

El requisito de OFF que imprima el lista completa de vértices antes de imprimir los triángulos significa que tengo que mantener la lista de triángulos en memoria antes de escribir la salida al archivo. Mientras tanto estoy recibiendo errores de memoria debido a los tamaños de las listas.

¿Cuál es la mejor manera de decirle a Python que ya no necesito algunos de los datos, y que puede ser liberado?

Author: Aaron Hall, 2009-08-22

9 answers

De acuerdo con la Documentación Oficial de Python, puede forzar al Recolector de basura a liberar memoria no referenciada con gc.collect(). Ejemplo:

import gc
gc.collect()
 244
Author: Havenard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-16 16:00:45

Desafortunadamente (dependiendo de su versión y lanzamiento de Python) algunos tipos de objetos usan "listas libres" que son una optimización local ordenada pero pueden causar fragmentación de memoria, específicamente al hacer que más y más memoria esté "reservada" solo para objetos de un cierto tipo y, por lo tanto, no disponible para el "fondo general".

La única manera realmente confiable de asegurar que un uso grande pero temporal de la memoria devuelva todos los recursos al sistema cuando se hace, es que ese uso ocurra en un el subproceso, que hace el trabajo hambriento de memoria, termina. Bajo tales condiciones, el sistema operativo hará su trabajo, y con gusto reciclará todos los recursos que el subproceso pueda haber engullido. Afortunadamente, el módulo multiprocessing hace que este tipo de operación (que solía ser más bien un dolor) no sea tan mala en las versiones modernas de Python.

En su caso de uso, parece que la mejor manera para que los subprocesos acumulen algunos resultados y, sin embargo, garantizar que esos resultados estén disponibles para el proceso principal es usar archivos semi-temporales (por semi-temporales quiero decir, NO el tipo de archivos que desaparecen automáticamente cuando se cierran, solo archivos ordinarios que se eliminan explícitamente cuando se termina con ellos).

 90
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-30 12:59:14

La instrucción del podría ser útil, pero IIRC no garantiza que libere la memoria. Los documentos están aquí... y un por qué no se libera está aquí.

He escuchado a personas en sistemas Linux y Unix bifurcando un proceso python para hacer algún trabajo, obteniendo resultados y luego matándolo.

Este artículo tiene notas sobre el recolector de basura de Python, pero creo que la falta de control de memoria es la desventaja de la memoria administrada

 28
Author: Aiden Bell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-22 21:54:48

Python es basura recolectada, por lo que si reduce el tamaño de su lista, recuperará memoria. También puede usar la instrucción " del " para deshacerse de una variable por completo:

biglist = [blah,blah,blah]
#...
del biglist
 20
Author: Ned Batchelder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-22 19:14:30

No se puede liberar memoria explícitamente. Lo que necesitas hacer es asegurarte de no guardar referencias a objetos. Entonces serán basura recolectada, liberando la memoria.

En su caso, cuando necesita listas grandes, normalmente necesita reorganizar el código, normalmente utilizando generadores/iteradores en su lugar. De esa manera no necesita tener las listas grandes en la memoria en absoluto.

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

 16
Author: Lennart Regebro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-22 19:16:44

(La liberación de memoria se realiza periódicamente de forma automática. del puede ser tu amigo, ya que marca objetos como eliminables.)

Tal vez no se encontraría con ningún problema de memoria en primer lugar mediante el uso de una estructura más compacta para sus datos. Por lo tanto, las listas de números son mucho menos eficientes en memoria que el formato utilizado por el módulo estándar array o el módulo de terceros numpy. Usted ahorraría memoria poniendo sus vértices en una matriz NumPy 3xN y sus triángulos en un N-elemento matriz.

 14
Author: Eric Lebigot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-07 20:30:59

Otros han publicado algunas formas en las que usted podría ser capaz de "convencer" al intérprete de Python para que libere la memoria (o de lo contrario evitar tener problemas de memoria). Lo más probable es que usted debe probar sus ideas primero. Sin embargo, creo que es importante darle una respuesta directa a su pregunta.

No hay realmente ninguna manera de decirle directamente a Python que libere memoria. El hecho de que la materia es que si desea que un bajo nivel de control, vas a tener que escribir una extensión en C o C++.

Dicho esto, hay algunas herramientas para ayudar con esto:

 7
Author: Jason Baker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-22 19:47:09

Tuve un problema similar al leer un gráfico de un archivo. El procesamiento incluyó el cálculo de una matriz flotante de 200 000x200 000 (una línea a la vez) que no cabía en la memoria. Tratando de liberar la memoria entre cálculos usando gc.collect() se solucionó el aspecto relacionado con la memoria del problema, pero resultó en problemas de rendimiento: No se por qué, pero a pesar de que la cantidad de memoria utilizada se mantuvo constante, cada nueva llamada a gc.collect() tomó algo más de tiempo que la anterior. Así que muy rápidamente el la recolección de basura tomó la mayor parte del tiempo de cálculo.

Para solucionar los problemas de memoria y rendimiento, cambié al uso de un truco de multiproceso que leí una vez en algún lugar (lo siento, ya no puedo encontrar el post relacionado). Antes estaba leyendo cada línea del archivo en un gran bucle for, procesándolo y ejecutando gc.collect() de vez en cuando para liberar espacio de memoria. Ahora llamo a una función que lee y procesa un trozo del archivo en un nuevo hilo. Una vez que el hilo termina, la memoria es liberado automáticamente sin el extraño problema de rendimiento.

Prácticamente funciona así:

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
 6
Author: Retzod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-21 06:15:17

Si no le importa la reutilización de vértices, podría tener dos archivos de salida one uno para vértices y otro para triángulos. Luego agregue el archivo de triángulo al archivo de vértice cuando haya terminado.

 3
Author: Nosredna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-22 19:29:19