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




limpiar variables en python (6)

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. procesar el archivo y crear una lista de triángulos, representados por sus vértices
  3. envía los vértices en el formato DESACTIVADO: 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 DESACTIVAR que imprima la lista completa de vértices antes de imprimir los triángulos significa que tengo que mantener la lista de triángulos en la memoria antes de escribir la salida en el archivo. Mientras tanto, recibo errores de memoria debido al tamaño de las listas.

¿Cuál es la mejor manera de decirle a Python que ya no necesito algunos datos y que puedo liberarlos?

https://code.i-harness.com


(El puede ser su amigo, ya que marca los objetos como eliminables cuando no hay otras referencias a ellos. Ahora, a menudo el intérprete de CPython conserva esta memoria para su uso posterior, por lo que es posible que su sistema operativo no vea la memoria "liberada").

Tal vez no se encontraría con ningún problema de memoria en primer lugar al usar 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 de array estándar o el módulo de numpy terceros. Usted ahorraría memoria colocando sus vértices en una matriz NumPy 3xN y sus triángulos en una matriz de N elementos.


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

La única manera realmente confiable de garantizar que un uso extenso pero temporal de la memoria devuelva todos los recursos al sistema cuando se realiza, es hacer que ese uso ocurra en un subproceso, que luego termina el trabajo que requiere mucha memoria. Bajo tales condiciones, el sistema operativo Hará su trabajo, y con mucho gusto reciclará todos los recursos que el subproceso pueda haber engullido. Afortunadamente, el módulo de 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 de que los subprocesos acumulen algunos resultados y, a la vez, garantice que esos resultados estén disponibles para el proceso principal es usar archivos semipremporales (por semitemporal quiero decir, NO el tipo de archivos que desaparece automáticamente cuando está cerrado, solo archivos ordinarios que borra explícitamente cuando termina con ellos).


No puedes liberar explícitamente la memoria. Lo que debe hacer es asegurarse de no mantener referencias a los objetos. Luego serán recolectados, liberando la memoria.

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

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


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

Realmente no hay manera de decirle directamente a Python que libere memoria. El hecho es que si desea un bajo nivel de control, tendrá que escribir una extensión en C o C ++.

Dicho esto, hay algunas herramientas para ayudar con esto:


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


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. Intentar liberar la memoria entre los cálculos utilizando gc.collect() corrigió el aspecto del problema relacionado con la memoria, pero dio lugar a problemas de rendimiento: no sé por qué, pero aunque la cantidad de memoria utilizada permanece constante, cada nueva llamada a gc.collect() tomó más tiempo que el anterior. Tan rápidamente, 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 subprocesos múltiples que leí una vez en alguna parte (lo siento, ya no puedo encontrar la publicación relacionada). Antes de leer cada línea del archivo en un bucle grande, lo procesaba y ejecutaba gc.collect() vez en cuando para liberar espacio en la memoria. Ahora llamo a una función que lee y procesa una parte del archivo en un nuevo hilo. Una vez que el hilo finaliza, la memoria se libera 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




memory-management