web-dev-qa-db-fra.com

ProcessPoolExecutor de concurrent.futures est beaucoup plus lent que le multiprocessing.

J'expérimentais avec le nouveau module brillant concurrent.futures introduit dans Python 3.2, et j'ai remarqué que, presque avec un code identique, en utilisant le Pool de concurrent. futures est beaucoup plus lent que d'utiliser multiprocessing.Pool .

Il s'agit de la version utilisant le multitraitement:

def hard_work(n):
    # Real hard work here
    pass

if __name__ == '__main__':
    from multiprocessing import Pool, cpu_count

    try:
        workers = cpu_count()
    except NotImplementedError:
        workers = 1
    pool = Pool(processes=workers)
    result = pool.map(hard_work, range(100, 1000000))

Et cela utilise concurrent.futures:

def hard_work(n):
    # Real hard work here
    pass

if __name__ == '__main__':
    from concurrent.futures import ProcessPoolExecutor, wait
    from multiprocessing import cpu_count
    try:
        workers = cpu_count()
    except NotImplementedError:
        workers = 1
    pool = ProcessPoolExecutor(max_workers=workers)
    result = pool.map(hard_work, range(100, 1000000))

En utilisant une fonction de factorisation naïve tirée de cela article Eli Bendersky , voici les résultats sur mon ordinateur (i7, 64 bits, Arch Linux):

[juanlu@nebulae]─[~/Development/Python/test]
└[10:31:10] $ time python pool_multiprocessing.py 

real    0m10.330s
user    1m13.430s
sys 0m0.260s
[juanlu@nebulae]─[~/Development/Python/test]
└[10:31:29] $ time python pool_futures.py 

real    4m3.939s
user    6m33.297s
sys 0m54.853s

Je ne peux pas les profiler avec le profileur Python car je reçois des erreurs de pickle. Des idées?

40
astrojuanlu

Lorsque vous utilisez map à partir de concurrent.futures, Chaque élément de l'itérable est soumis séparément à l'exécuteur, ce qui crée un Future = objet pour chaque appel. Il retourne ensuite un itérateur qui donne les résultats retournés par les futures.
Future les objets sont plutôt lourds, ils font beaucoup de travail pour autoriser toutes les fonctionnalités qu'ils fournissent (comme les rappels, la possibilité d'annuler, de vérifier l'état, ... ).

Par rapport à cela, multiprocessing.Pool A beaucoup moins de frais généraux. Il soumet les travaux par lots (en réduisant IPC surcharge) et utilise directement le résultat renvoyé par la fonction. Pour les gros lots de travaux, le multitraitement est certainement la meilleure option.

Les avenirs sont parfaits si vous souhaitez résumer des travaux de longue durée où les frais généraux ne sont pas si importants, où vous souhaitez être averti par rappel ou vérifier de temps en temps pour voir s'ils sont terminés ou pouvoir annuler l'exécution individuellement.

Note personnelle :

Je ne peux pas vraiment penser à beaucoup de raisons d'utiliser Executor.map - il ne vous donne aucune des fonctionnalités des futures - à l'exception de la possibilité de spécifier un délai d'attente. Si les résultats vous intéressent, il vaut mieux utiliser l'une des fonctions cartographiques de multiprocessing.Pool.

55
mata