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?
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
.