python - ProcessPoolExecutor de concurrent.futures es mucho más lento que el multiprocesamiento.
concurrency multiprocessing (1)
Cuando se usa map
from concurrent.futures
, cada elemento del iterable se envía por separado al ejecutor, lo que crea un objeto Future
para cada llamada. A continuación, devuelve un iterador que produce los resultados devueltos por los futuros.
Future
objetos Future
son bastante pesados, hacen mucho trabajo para permitir todas las funciones que proporcionan (como devoluciones de llamada, capacidad de cancelar, verificar el estado, ...).
Comparado con eso, el multiprocessing.Pool
tiene mucho menos sobrecarga. Envía trabajos en lotes (reduciendo la sobrecarga de IPC) y utiliza directamente el resultado devuelto por la función. Para grandes lotes de trabajos, el multiprocesamiento es definitivamente la mejor opción.
Los futuros son excelentes si desea agregar trabajos de larga duración en los que la sobrecarga no es tan importante, donde desea que se le notifique mediante una devolución de llamada o un chequeo de vez en cuando para ver si están listos o poder cancelar la ejecución individualmente.
Nota personal :
Realmente no puedo pensar en muchas razones para usar Executor.map
(no le ofrece ninguna de las características de los futuros), excepto la posibilidad de especificar un tiempo de espera. Si solo está interesado en los resultados, es mejor que utilice una de multiprocessing.Pool
. Funciones de mapa de Pool.
Estaba experimentando con el nuevo y brillante módulo concurrent.futures introducido en Python 3.2, y he notado que, casi con código idéntico, usar Pool de concurrent.futures es mucho más lento que usar multiprocessing.Pool .
Esta es la versión que utiliza multiprocesamiento:
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))
Y esto es usar 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))
Usando una función de factorización ingenua tomada de este artículo de Eli Bendersky , estos son los resultados en mi computadora (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
No puedo hacer un perfil de estos con el generador de perfiles de Python porque recibo errores de pickle. ¿Algunas ideas?