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. Tratar de liberar la memoria entre los cálculos utilizando gc.collect()
el aspecto fijo del problema relacionado con la memoria, pero resultó en problemas de rendimiento: no sé por qué, pero a pesar de que la cantidad de memoria utilizada permaneció constante, cada nueva llamada gc.collect()
tomó más tiempo que El anterior. Así que bastante rápido 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 algún lugar (lo siento, ya no puedo encontrar la publicación relacionada). Antes de leer cada línea del archivo en un gran for
bucle, procesarlo y ejecutarlo de gc.collect()
vez en cuando para liberar espacio en la memoria. Ahora llamo a una función que lee y procesa un fragmento del archivo en un nuevo hilo. Una vez que finaliza el subproceso, 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