20

Stavo sperimentando con il nuovo modulo lucido concurrent.futures introdotto in Python 3.2, e ho notato che, quasi con codice identico, usare Pool da concurrent.futures è modo più lento rispetto all'utilizzo di multiprocessing.Pool.ProcessPoolExecutor da concurrent.futures molto più lento di multiprocessing.Pool

Questa è la versione che utilizza multiprocessing:

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)) 

E questo sta usando 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)) 

utilizzando una funzione di fattorizzazione ingenuo preso da questo Eli Bendersky article, questi sono i risultati sul mio computer (i7, 64-bit, Arch Linux):

[[email protected]]─[~/Development/Python/test] 
└[10:31:10] $ time python pool_multiprocessing.py 

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

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

Non riesco a profilarli con il profiler Python perché ottengo errori di pickle. Qualche idea?

+1

Mi piace la tua convenzione di denominazione, in particolare 'worker' e' hard_work': P –

+0

Cool, innit? : P – astrojuanlu

risposta

34

Quando si utilizza map da concurrent.futures, ogni elemento dal iterable is submitted separatamente l'esecutore, che crea un oggetto Future per ogni chiamata. Quindi restituisce un iteratore che produce i risultati restituiti dai futures.
Future Gli oggetti sono piuttosto pesanti, fanno molto lavoro per consentire tutte le funzionalità che forniscono (come callback, possibilità di annullare, controllare lo stato, ...).

Rispetto a quello, multiprocessing.Pool ha molto meno spese generali. Invia lavori in lotti (riducendo l'overhead IPC) e utilizza direttamente il risultato restituito dalla funzione. Per grandi quantità di lavori, la multiprocessing è sicuramente la scelta migliore.

I futures sono grandi se si desidera riassumere lavori di lunga durata in cui il sovraccarico non è così importante, in cui si desidera essere avvisati tramite richiamata o controllare di volta in volta per vedere se sono terminati o essere in grado di annullare l'esecuzione individualmente.

Nota personale:

non posso davvero pensare a molto motivi per utilizzare Executor.map - che non ti dà nessuna delle caratteristiche dei contratti future - fatta eccezione per la possibilità di specificare un timeout. Se sei solo interessato ai risultati, è meglio usare una delle funzioni di mappa di multiprocessing.Pool.

+0

Grazie mille per la risposta! Probabilmente la presentazione in lotti è la cosa fondamentale qui. – astrojuanlu

+7

Per quello che vale, in Python 3.5, 'ProcessPoolExecutor.map' accetterà un argomento di parola' chunksize', che allevierà un po 'il problema del sovraccarico IPC. Vedi questo [bug] (http://bugs.python.org/issue11271) per maggiori informazioni. – dano

+0

Inoltre, in Python 3.2 è possibile impostare _maxtasksperchild_ per un pool multiprocesso che, nel mio caso, ha aiutato a ripulire le risorse dopo che ciascun lavoratore ha terminato il proprio carico di lavoro. [Link] (https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool) – Kieleth