python concurrency multiprocessing future concurrent.futures

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?