multiprocessing.Pool: Quelle est la différence entre map_async et imap?

191

J'essaie d'apprendre à utiliser le multiprocessingpackage de Python , mais je ne comprends pas la différence entre map_asyncet imap. J'ai remarqué que les deux map_asyncet imapsont exécutés de manière asynchrone. Alors, quand devrais-je utiliser l'un sur l'autre? Et comment récupérer le résultat renvoyé par map_async?

Dois-je utiliser quelque chose comme ça?

def test():
    result = pool.map_async()
    pool.close()
    pool.join()
    return result.get()

result=test()
for i in result:
    print i
espace
la source

Réponses:

513

Il existe deux différences clés entre imap/ imap_unorderedet map/ map_async:

  1. La façon dont ils consomment l'itérable que vous leur transmettez.
  2. La façon dont ils vous renvoient le résultat.

mapconsomme votre itérable en convertissant l'itérable en liste (en supposant que ce n'est pas déjà une liste), en le divisant en morceaux et en envoyant ces morceaux aux processus de travail dans le Pool. La division de l'itérable en morceaux est plus efficace que le passage de chaque élément de l'itérable entre les processus, un élément à la fois, en particulier si l'itérable est volumineux. Cependant, transformer l'itérable en liste afin de le fragmenter peut avoir un coût de mémoire très élevé, car la liste entière devra être conservée en mémoire.

imapne transforme pas l'itérable que vous lui donnez en liste, ni ne le divise en morceaux (par défaut). Il itérera sur l'élément itérable un par un, et les enverra chacun à un processus de travail. Cela signifie que vous ne prenez pas le coup de mémoire de la conversion de l'ensemble de l'itérable en liste, mais cela signifie également que les performances sont plus lentes pour les grands itérables, en raison du manque de segmentation. Cela peut être atténué en passant un chunksizeargument plus grand que la valeur par défaut de 1, cependant.

L'autre différence majeure entre imap/ imap_unorderedet map/ map_async, c'est qu'avec imap/ imap_unordered, vous pouvez commencer à recevoir les résultats des travailleurs dès qu'ils sont prêts, plutôt que d'avoir à attendre qu'ils soient tous terminés. Avec map_async, an AsyncResultest renvoyé tout de suite, mais vous ne pouvez pas réellement récupérer les résultats de cet objet tant qu'ils n'ont pas tous été traités, auquel cas il renvoie la même liste que map( mapest en fait implémentée en interne map_async(...).get()). Il n'y a aucun moyen d'obtenir des résultats partiels; soit vous avez le résultat complet, soit rien.

imapet les imap_unordereddeux renvoient les itérables immédiatement. Avec imap, les résultats seront générés à partir de l'itérable dès qu'ils sont prêts, tout en préservant l'ordre de l'itérable d'entrée. Avec imap_unordered, les résultats seront générés dès qu'ils seront prêts, quel que soit l'ordre de l'itération d'entrée. Alors, dites que vous avez ceci:

import multiprocessing
import time

def func(x):
    time.sleep(x)
    return x + 2

if __name__ == "__main__":    
    p = multiprocessing.Pool()
    start = time.time()
    for x in p.imap(func, [1,5,3]):
        print("{} (Time elapsed: {}s)".format(x, int(time.time() - start)))

Cela produira:

3 (Time elapsed: 1s)
7 (Time elapsed: 5s)
5 (Time elapsed: 5s)

Si vous utilisez à la p.imap_unorderedplace de p.imap, vous verrez:

3 (Time elapsed: 1s)
5 (Time elapsed: 3s)
7 (Time elapsed: 5s)

Si vous utilisez p.mapou p.map_async().get(), vous verrez:

3 (Time elapsed: 5s)
7 (Time elapsed: 5s)
5 (Time elapsed: 5s)

Ainsi, les principales raisons d'utiliser imap/ imap_unorderedover map_asyncsont:

  1. Votre itérable est suffisamment volumineux pour que le convertir en liste vous fasse manquer / utiliser trop de mémoire.
  2. Vous voulez être en mesure de commencer à traiter les résultats avant tout d'entre eux sont terminés.
Dano
la source
1
qu'en est-il de apply et apply_async?
Harsh Daftary
10
@HarshDaftary applyenvoie une seule tâche à un processus de travail, puis se bloque jusqu'à ce qu'elle soit terminée. apply_asyncenvoie une seule tâche à un processus de travail, puis renvoie immédiatement un AsyncResultobjet, qui peut être utilisé pour attendre la fin de la tâche et récupérer le résultat. applyest implémenté en appelant simplementapply_async(...).get()
dano
59
C'est le genre de description qui devrait figurer dans la Pooldocumentation officielle plutôt que dans la documentation ennuyeuse existante .
min
1
@BallpointBen Il passera au travail suivant dès qu'il sera terminé. La commande est traitée dans le processus parent.
dano
1
Que se passe-t-il si vous ne vous souciez pas du tout de renvoyer un résultat et que, par exemple, les résultats du processus sont écrits sur le disque pour une utilisation ultérieure?
Tanner