web-dev-qa-db-fra.com

L'utilisation de la mémoire continue de croître avec le multiprocessing.pool de Python

Voici le programme:

#!/usr/bin/python

import multiprocessing

def dummy_func(r):
    pass

def worker():
    pass

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=16)
    for index in range(0,100000):
        pool.apply_async(worker, callback=dummy_func)

    # clean up
    pool.close()
    pool.join()

J'ai trouvé que l'utilisation de la mémoire (VIRT et RES) continuait de croître jusqu'à close ()/join (), y a-t-il une solution pour s'en débarrasser? J'ai essayé maxtasksperchild avec 2.7 mais cela n'a pas aidé non plus.

J'ai un programme plus compliqué qui appelle apply_async () ~ 6M fois, et à ~ 1,5M point j'ai déjà 6G + RES, pour éviter tous les autres facteurs, j'ai simplifié le programme à la version ci-dessus.

MODIFIER:

Il s'est avéré que cette version fonctionne mieux, merci pour la contribution de tout le monde:

#!/usr/bin/python

import multiprocessing

ready_list = []
def dummy_func(index):
    global ready_list
    ready_list.append(index)

def worker(index):
    return index

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=16)
    result = {}
    for index in range(0,1000000):
        result[index] = (pool.apply_async(worker, (index,), callback=dummy_func))
        for ready in ready_list:
            result[ready].wait()
            del result[ready]
        ready_list = []

    # clean up
    pool.close()
    pool.join()

Je n'y ai mis aucun verrou car je pense que le processus principal est monothread (le rappel est plus ou moins comme une chose événementielle par document que j'ai lu).

J'ai changé la plage d'index de v1 à 1000000, comme v2 et j'ai fait quelques tests - c'est bizarre pour moi, v2 est même ~ 10% plus rapide que v1 (33s contre 37s), peut-être que v1 faisait trop de travaux de maintenance de liste interne. v2 est définitivement un gagnant sur l'utilisation de la mémoire, il n'a jamais dépassé 300M (VIRT) et 50M (RES), alors que v1 était auparavant 370M/120M, le meilleur était 330M/85M. Tous les nombres n'étaient que 3 à 4 fois testés, référence seulement.

27
C.B.

J'ai eu des problèmes de mémoire récemment, car j'utilisais plusieurs fois la fonction de multitraitement, donc elle continue de générer des processus et les laisse en mémoire.

Voici la solution que j'utilise maintenant:

def myParallelProcess(ahugearray)
 from multiprocessing import Pool
 from contextlib import closing
 with closing( Pool(15) ) as p:
    res = p.imap_unordered(simple_matching, ahugearray, 100)
 return res

I ❤ avec

19
deddu

Utilisation map_async au lieu de apply_async pour éviter une utilisation excessive de la mémoire.

Pour votre premier exemple, modifiez les deux lignes suivantes:

for index in range(0,100000):
    pool.apply_async(worker, callback=dummy_func)

à

pool.map_async(worker, range(100000), callback=dummy_func)

Il se terminera en un clin d'œil avant que vous puissiez voir son utilisation de la mémoire dans top. Remplacez la liste par une plus grande pour voir la différence. Mais notez map_async convertira d'abord l'itérable que vous lui passez en liste pour calculer sa longueur s'il n'a pas __len__ méthode. Si vous avez un itérateur d'un grand nombre d'éléments, vous pouvez utiliser itertools.islice pour les traiter en petits morceaux.

J'ai eu un problème de mémoire dans un programme réel avec beaucoup plus de données et j'ai finalement trouvé que le coupable était apply_async.

P.S., en ce qui concerne l'utilisation de la mémoire, vos deux exemples n'ont pas de différence évidente.

6
Fish Monitor

Créez simplement le pool dans votre boucle et fermez-le à la fin de la boucle avec pool.close().

5
Ullullu

J'ai un très grand ensemble de données de nuages ​​de points 3D que je traite. J'ai essayé d'utiliser le module multitraitement pour accélérer le traitement, mais j'ai commencé à sortir des erreurs de mémoire. Après quelques recherches et tests, j'ai déterminé que je remplissais la file d'attente des tâches à traiter beaucoup plus rapidement que les sous-processus ne pouvaient la vider. Je suis sûr qu'en fragmentant, ou en utilisant map_async ou quelque chose, j'aurais pu ajuster la charge, mais je ne voulais pas apporter de changements majeurs à la logique environnante.

La solution idiote que j'ai trouvée consiste à vérifier le pool._cache longueur par intermittence, et si le cache est trop volumineux, attendez que la file d'attente soit vide.

Dans ma boucle principale, j'avais déjà un compteur et un ticker d'état:

# Update status
count += 1
if count%10000 == 0:
    sys.stdout.write('.')
    if len(pool._cache) > 1e6:
        print "waiting for cache to clear..."
        last.wait() # Where last is assigned the latest ApplyResult

Donc, à chaque insertion de 10 000 dans le pool, je vérifie s'il y a plus d'un million d'opérations en file d'attente (environ 1 Go de mémoire utilisée dans le processus principal). Lorsque la file d'attente est pleine, j'attends juste que le dernier travail inséré se termine.

Maintenant, mon programme peut fonctionner pendant des heures sans manquer de mémoire. Le processus principal s'arrête de temps en temps pendant que les travailleurs continuent de traiter les données.

BTW le membre _cache est documenté l'exemple de pool de modules de multiprocessing:

#
# Check there are no outstanding tasks
#

assert not pool._cache, 'cache = %r' % pool._cache
4
kitsu.eb

Je pense que c'est similaire à la question que j'ai postée , mais je ne suis pas sûr que vous ayez le même délai. Mon problème était que je produisais des résultats à partir du pool de multitraitement plus rapidement que je ne les consommais, donc ils se sont accumulés en mémoire. Pour éviter cela, j'ai utilisé un sémaphore pour étrangler les entrées dans le pool afin qu'elles n'aillent pas trop loin devant les sorties que je consommais.

1
Don Kirkby