J'ai écrit un programme Python qui agit sur un grand fichier d'entrée pour créer quelques millions d'objets représentant des triangles. L'algorithme est:
L’obligation pour OFF d’imprimer la liste complète des sommets avant d’imprimer les triangles signifie que je dois conserver la liste des triangles en mémoire avant d’écrire la sortie dans le fichier. En attendant, je reçois des erreurs de mémoire à cause de la taille des listes.
Quel est le meilleur moyen de dire à Python que je n'ai plus besoin de certaines données et qu'elles peuvent être libérées?
Selon documentation officielle Python , vous pouvez forcer le récupérateur de place à libérer de la mémoire non référencée avec gc.collect()
. Exemple:
import gc
gc.collect()
Malheureusement (en fonction de votre version et de votre version de Python), certains types d’objets utilisent des "listes libres" qui constituent une optimisation locale ordonnée mais peuvent provoquer une fragmentation de la mémoire, notamment en faisant de plus en plus de mémoire "réservée" pour les objets d’un certain type et ainsi indisponible pour le "fonds général".
Le seul moyen réellement fiable de garantir qu'une utilisation importante mais temporaire de la mémoire restitue toutes les ressources au système lorsque cette opération est terminée consiste à effectuer cette utilisation dans un sous-processus, ce qui met fin au travail gourmand en mémoire. Dans ces conditions, le système d’exploitation fera son travail et recyclera volontiers toutes les ressources que le sous-processus a pu engloutir. Heureusement, le module multiprocessing
rend ce type d'opération (qui était plutôt pénible) dans les versions modernes de Python.
Dans votre cas d'utilisation, il semble que le meilleur moyen pour les sous-processus d'accumuler certains résultats tout en garantissant que ces résultats sont disponibles pour le processus principal consiste à utiliser des fichiers semi-temporaires (par semi-temporaire, je veux dire, PAS le type de fichiers disparaissent automatiquement à la fermeture, uniquement les fichiers ordinaires que vous supprimez explicitement lorsque vous en avez terminé).
L'instruction del
pourrait être utile, mais IIRC ne libère pas la mémoire . Le les documents sont ici ... et un pourquoi il n'est pas publié est ici .
J'ai entendu des personnes sur des systèmes de type Linux et Unix forger un processus python pour effectuer un travail, obtenir des résultats puis le détruire.
Cet article a des notes sur le Python garbage collector, mais je pense que l'absence de contrôle de la mémoire est l'inconvénient de la mémoire gérée
Python est récupéré, donc si vous réduisez la taille de votre liste, il récupérera de la mémoire. Vous pouvez également utiliser l'instruction "del" pour supprimer complètement une variable:
biglist = [blah,blah,blah]
#...
del biglist
Vous ne pouvez pas explicitement libérer de la mémoire. Ce que vous devez faire, c'est vous assurer de ne pas conserver de références à des objets. Ils seront ensuite ramassés, libérant la mémoire.
Dans votre cas, lorsque vous avez besoin de listes volumineuses, vous devez généralement réorganiser le code, en utilisant généralement des générateurs/itérateurs. De cette façon, vous n'avez pas du tout besoin d'avoir de grandes listes en mémoire.
http://www.prasannatech.net/2009/07/introduction-python-generators.html
(del
peut être votre ami, car il marque les objets comme pouvant être supprimés lorsqu'il n'y a aucune autre référence. Maintenant, souvent, l'interpréteur CPython conserve cette mémoire pour une utilisation ultérieure. Par conséquent, votre système d'exploitation risque de ne pas voir le "libéré". Mémoire.)
Vous ne rencontreriez peut-être aucun problème de mémoire en utilisant une structure plus compacte pour vos données. Ainsi, les listes de nombres utilisent beaucoup moins de mémoire que le format utilisé par le module standard array
ou le module tiers numpy
. Vous économiserez de la mémoire en plaçant vos sommets dans un tableau NumPy 3xN et vos triangles dans un tableau à N éléments.
D'autres ont déjà indiqué que vous pourriez être en mesure de "convaincre" l'interprète Python de libérer de la mémoire (ou d'éviter autrement d'avoir des problèmes de mémoire). Les chances sont que vous devriez essayer leurs idées en premier. Cependant, j'estime qu'il est important de vous donner une réponse directe à votre question.
Il n’ya vraiment aucun moyen de dire directement à Python de libérer de la mémoire. Le fait est que si vous voulez un niveau de contrôle aussi bas, vous devrez écrire une extension en C ou C++.
Cela dit, il existe quelques outils pour vous aider:
J'ai eu un problème similaire en lisant un graphique à partir d'un fichier. Le traitement incluait le calcul d’une matrice flottante 200 000 x 200 000 (une ligne à la fois) qui n’était pas mémorisée. Tenter de libérer la mémoire entre les calculs à l’aide de gc.collect()
a corrigé le problème lié à la mémoire, mais des problèmes de performances se sont produits: je ne sais pas pourquoi, mais même si la quantité de mémoire utilisée est restée constante, chaque nouvel appel à gc.collect()
a pris un peu plus de temps que le précédent. Donc, assez rapidement, la collecte des ordures a pris la majeure partie du temps de calcul.
Pour résoudre à la fois les problèmes de mémoire et de performances, je suis passé à l'utilisation d'une astuce multithreading que j'ai lue une fois quelque part (je suis désolée, je ne trouve plus l'article associé). Avant de lire chaque ligne du fichier dans une grande boucle for
, de la traiter et de lancer gc.collect()
de temps en temps pour libérer de l'espace mémoire. Maintenant, j'appelle une fonction qui lit et traite une partie du fichier dans un nouveau thread. Une fois le thread terminé, la mémoire est automatiquement libérée sans le problème de performances étrange.
Pratiquement cela fonctionne comme ceci:
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
Si vous ne vous souciez pas de la réutilisation des vertex, vous pouvez avoir deux fichiers de sortie - un pour les vertices et un pour les triangles. Ensuite, ajoutez le fichier triangle au fichier sommet lorsque vous avez terminé.