J'essaie d'itérer plus de 100 000 images et de capturer certaines fonctionnalités d'image et de stocker le dataFrame résultant sur le disque sous forme de fichier de pickle.
Malheureusement, en raison de contraintes RAM, je suis obligé de diviser les images en morceaux de 20 000 et d'effectuer des opérations dessus avant d'enregistrer les résultats sur le disque.
Le code écrit ci-dessous est censé enregistrer la trame de données des résultats pour 20 000 images avant de démarrer la boucle pour traiter les 20 000 images suivantes.
Cependant - Cela ne semble pas résoudre mon problème car la mémoire n'est pas libérée de RAM à la fin de la première boucle for
Donc, quelque part lors du traitement du 50 000e enregistrement, le programme se bloque en raison d'une erreur de mémoire insuffisante.
J'ai essayé de supprimer les objets après les avoir enregistrés sur le disque et d'invoquer le garbage collector, mais l'utilisation de RAM ne semble pas baisser).
Qu'est-ce que je rate?
#file_list_1 contains 100,000 images
file_list_chunks = list(divide_chunks(file_list_1,20000))
for count,f in enumerate(file_list_chunks):
# make the Pool of workers
pool = ThreadPool(64)
results = pool.map(get_image_features,f)
# close the pool and wait for the work to finish
list_a, list_b = Zip(*results)
df = pd.DataFrame({'filename':list_a,'image_features':list_b})
df.to_pickle("PATH_TO_FILE"+str(count)+".pickle")
del list_a
del list_b
del df
gc.collect()
pool.close()
pool.join()
print("pool closed")
Maintenant, il se pourrait que quelque chose dans le 50 000e soit très grand, et cela cause le MOO, donc pour tester cela, j'essaierais d'abord:
file_list_chunks = list(divide_chunks(file_list_1,20000))[30000:]
S'il échoue à 10 000, cela confirmera si 20k est trop gros, ou s'il échoue à nouveau à 50 000, il y a un problème avec le code ...
D'accord, sur le code ...
Tout d'abord, vous n'avez pas besoin du constructeur explicite list
, c'est beaucoup mieux dans python pour itérer plutôt que de générer l'intégralité de la liste en mémoire.
file_list_chunks = list(divide_chunks(file_list_1,20000))
# becomes
file_list_chunks = divide_chunks(file_list_1,20000)
Je pense que vous pourriez abuser de ThreadPool ici:
Empêche la soumission d'autres tâches au pool. Une fois toutes les tâches terminées, les processus de travail se termineront.
Cela se lit comme close
pourrait avoir quelques réflexions en cours d'exécution, bien que je suppose que cela est sûr, il semble un peu non-Pythonic, il est préférable d'utiliser le gestionnaire de contexte pour ThreadPool:
with ThreadPool(64) as pool:
results = pool.map(get_image_features,f)
# etc.
Les del
s explicites dans python ne sont pas réellement garantis pour libérer de la mémoire .
Vous devez collecter après la jointure/après le avec:
with ThreadPool(..):
...
pool.join()
gc.collect()
Vous pouvez également essayer de le découper en morceaux plus petits, par exemple 10000 ou même plus petit!
Une chose que j'envisagerais de faire ici, au lieu d'utiliser pandas DataFrames et grandes listes est d'utiliser une base de données SQL, vous pouvez le faire localement avec sqlite :
import sqlite3
conn = sqlite3.connect(':memory:', check_same_thread=False) # or, use a file e.g. 'image-features.db'
et utilisez le gestionnaire de contexte:
with conn:
conn.execute('''CREATE TABLE images
(filename text, features text)''')
with conn:
# Insert a row of data
conn.execute("INSERT INTO images VALUES ('my-image.png','feature1,feature2')")
De cette façon, nous n'aurons pas à gérer les objets de grande liste ou DataFrame.
Vous pouvez passer la connexion à chacun des threads ... vous pourriez avoir quelque chose d'un peu bizarre comme:
results = pool.map(get_image_features, Zip(itertools.repeat(conn), f))
Ensuite, une fois le calcul terminé, vous pouvez tout sélectionner dans la base de données, dans le format que vous souhaitez. Par exemple. en utilisant read_sql .
Utilisez un sous-processus ici, plutôt que de l'exécuter dans la même instance de python "Shell out" vers un autre).
Puisque vous pouvez passer début et fin à python en tant que sys.args, vous pouvez les découper:
# main.py
# a for loop to iterate over this
subprocess.check_call(["python", "chunk.py", "0", "20000"])
# chunk.py a b
for count,f in enumerate(file_list_chunks):
if count < int(sys.argv[1]) or count > int(sys.argv[2]):
pass
# do stuff
De cette façon, le sous-processus nettoiera correctement python (il n'y a aucun moyen qu'il y ait des fuites de mémoire, car le processus sera terminé).
Je parie que Hammer 1 est le chemin à parcourir, on dirait que vous collez beaucoup de données et que vous les lisez dans python listes inutilement, et en utilisant sqlite3 (ou une autre base de données) ) évite complètement cela.
Remarque: ce n'est pas une réponse, mais plutôt une liste rapide de questions et suggestions
ThreadPool()
from multiprocessing.pool
? Ce n'est pas vraiment bien documenté (dans python3
) et je préfère utiliser ThreadPoolExecutor , (voir aussi ici )sys.getsizeof()
pour renvoyer une liste de tous les globals()
déclarés, ainsi que leur empreinte mémoire.del results
(même si cela ne devrait pas être trop grand, je suppose)Votre problème est que vous utilisez le threading où le multitraitement doit être utilisé (lié au processeur vs IO lié).
Je remanierais votre code un peu comme ceci:
from multiprocessing import Pool
if __name__ == '__main__':
cpus = multiprocessing.cpu_count()
with Pool(cpus-1) as p:
p.map(get_image_features, file_list_1)
puis je changerais la fonction get_image_features
en ajoutant (quelque chose comme) ces deux lignes à la fin. Je ne peux pas dire exactement comment vous traitez ces images, mais l'idée est de faire chaque image à l'intérieur de chaque processus, puis de la sauvegarder immédiatement sur le disque:
df = pd.DataFrame({'filename':list_a,'image_features':list_b})
df.to_pickle("PATH_TO_FILE"+str(count)+".pickle")
Ainsi, la trame de données sera décapée et enregistrée à l'intérieur de chaque processus, à la place après sa sortie. Les processus sont nettoyés de la mémoire dès leur sortie, donc cela devrait fonctionner pour garder l'empreinte mémoire faible.
pd.DataFrame(...)
peut fuir sur certaines versions de Linux (voir github problème et "solution" ), donc même del df
pourrait ne pas aider.
Dans votre cas, la solution de github peut être utilisée sans patch de singe de pd.DataFrame.__del__
:
from ctypes import cdll, CDLL
try:
cdll.LoadLibrary("libc.so.6")
libc = CDLL("libc.so.6")
libc.malloc_trim(0)
except (OSError, AttributeError):
libc = None
if no libc:
print("Sorry, but pandas.DataFrame may leak over time even if it's instances are deleted...")
CHUNK_SIZE = 20000
#file_list_1 contains 100,000 images
with ThreadPool(64) as pool:
for count,f in enumerate(divide_chunks(file_list_1, CHUNK_SIZE)):
# make the Pool of workers
results = pool.map(get_image_features,f)
# close the pool and wait for the work to finish
list_a, list_b = Zip(*results)
df = pd.DataFrame({'filename':list_a,'image_features':list_b})
df.to_pickle("PATH_TO_FILE"+str(count)+".pickle")
del df
# 2 new lines of code:
if libc: # Fix leaking of pd.DataFrame(...)
libc.malloc_trim(0)
print("pool closed")
P.S. Cette solution n'aidera pas si une trame de données unique est trop volumineuse. Cela ne peut être aidé qu'en réduisant CHUNK_SIZE
En bref, vous ne pouvez pas libérer de mémoire dans l'interpréteur Python. Votre meilleur pari serait d'utiliser le multitraitement car chaque processus peut gérer la mémoire seul.
Le garbage collector va "libérer" de la mémoire, mais pas dans le contexte auquel vous vous attendez. La gestion des pages et des pools peut être explorée dans la source CPython. Il y a aussi un article de haut niveau ici: https://realpython.com/python-memory-management/
N'appelez PAS list (), cela crée une liste en mémoire de tout ce qui est retourné par divide_chunks (). C'est là que votre problème de mémoire se produit probablement.
Vous n'avez pas besoin de toutes ces données en mémoire à la fois. Parcourez les noms de fichiers un par un, de cette façon, toutes les données ne sont pas en mémoire à la fois.
Veuillez poster la trace de la pile afin que nous ayons plus d'informations
Je pense que ce sera possible avec céleri , grâce au céleri, vous pouvez facilement utiliser la simultanéité et le parallélisme avec python.
Le traitement des images semble idempotent et atomique, donc cela peut être un tâche céleri .
Vous pouvez exécuter quelques travailleurs qui traitera les tâches - travailler avec l'image.
De plus, il a configuration pour les fuites de mémoire.
Ma solution à ce genre de problèmes consiste à utiliser un outil de traitement parallèle. Je préfère joblib car il permet de paralléliser même des fonctions créées localement (qui sont des "détails d'implémentation" et donc il vaut mieux éviter de les rendre globales dans un module). Mon autre conseil: n'utilisez pas de threads (et de pools de threads) en python, utilisez plutôt des processus (et des pools de processus) - c'est presque toujours une meilleure idée! Assurez-vous simplement de créer un pool d'au moins 2 processus dans joblib, sinon il exécuterait tout dans le processus python et ainsi RAM ne serait pas publié) À la fin. Une fois que les processus de travail de joblib sont fermés automatiquement, RAM qu'ils ont alloué sera entièrement libéré par le système d'exploitation. Mon arme préférée de choix est joblib.Parallel . Si vous devez transférer des données volumineuses aux travailleurs (c'est-à-dire supérieures à 2 Go), utilisez joblib.dump (pour écrire un objet python dans un fichier du processus principal). ) et joblib.load (pour le lire dans un processus de travail).
À propos de del object
: En python, la commande ne supprime pas réellement un objet. Il diminue seulement son compteur de référence. Lorsque vous exécutez import gc; gc.collect()
, le garbage collector décide lui-même quelle mémoire libérer et laquelle laisser allouée, et je ne suis pas au courant d'un moyen de le forcer à libérer toute la mémoire possible. Pire encore, si de la mémoire a été allouée non pas par python mais, par exemple, dans certains codes C/C++/Cython/etc externes et que le code n'a pas associé de python compteur de référence avec la mémoire, il n'y aurait absolument rien que vous puissiez faire pour le libérer de l'intérieur de python, sauf ce que j'ai écrit ci-dessus, c'est-à-dire en mettant fin au processus python qui a alloué la RAM, auquel cas il serait garanti d'être libéré par l'OS. C'est pourquoi le seul moyen fiable à 100% pour libérer de la mémoire en python, est d'exécuter le code qui l'alloue dans un processus parallèle, puis pour terminer le processus .