20

Ich experimentierte mit dem neuen glänzenden concurrent.futures Modul in Python 3.2 eingeführt, und ich habe festgestellt, dass fast mit identischem Code, mit dem Pool von concurrent.futures ist Weg langsamer als mit multiprocessing.Pool.ProcessPoolExecutor von concurrent.futures viel langsamer als Multiprocessing.Pool

Dies ist die Version mit 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)) 

Und das ist mit 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)) 

eine naive Faktorisierung Funktion von diesem Eli Bendersky article genommen Verwendung, das sind die Ergebnisse auf meinem 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 

Ich kann diese nicht mit dem Python-Profiler profilieren, weil ich Pickle-Fehler erhalte. Irgendwelche Ideen?

+1

Ich liebe Ihre Namenskonvention, vor allem 'Worker' und' Hard_work': P –

+0

Cool, innit? : P – astrojuanlu

Antwort

34

Wenn map von concurrent.futures verwenden, wobei jedes Element aus den iterable is submitted getrennt dem Vollstrecker, die für jeden Anruf ein Future Objekt erstellt. Es gibt dann einen Iterator zurück, der die von den Futures zurückgegebenen Ergebnisse liefert.
Future Objekte sind eher Schwergewicht, sie machen eine Menge Arbeit, um alle Funktionen, die sie bieten (wie Rückrufe, Fähigkeit zu stornieren, Status überprüfen, ...) zu ermöglichen.

Im Vergleich dazu hat multiprocessing.Pool viel weniger Overhead. Sie übergibt Jobs in Stapeln (reduziert den IPC-Overhead) und verwendet direkt das von der Funktion zurückgegebene Ergebnis. Für große Stapel von Jobs ist Multiprocessing definitiv die bessere Option.

Futures sind ideal, wenn Sie lange laufende Aufträge verrichten möchten, bei denen der Overhead nicht so wichtig ist, wo Sie per Callback benachrichtigt werden oder von Zeit zu Zeit nachsehen, ob sie fertig sind oder abbrechen können die Ausführung einzeln.

Persönliche Anmerkung:

Ich kann nicht wirklich viele Gründe denken Executor.map zu verwenden - es ist nicht Ihnen eines der Merkmale von Termingeschäften nicht geben - mit Ausnahme der Fähigkeit, ein Timeout zu spezifizieren. Wenn Sie nur an den Ergebnissen interessiert sind, verwenden Sie besser eine der Kartenfunktionen von multiprocessing.Pool.

+0

Vielen Dank für Ihre Antwort! Wahrscheinlich ist das Einsenden in Batches hier das Wichtigste. – astrojuanlu

+7

Für was es wert ist, akzeptiert 'ProcessPoolExecutor.map' in Python 3.5 ein' chunksize' Schlüsselwortargument, das das IPC Overheadproblem etwas abmildert. Weitere Informationen finden Sie in diesem [Fehler] (http://bugs.python.org/issue11271). – dano

+0

In Python 3.2 können Sie auch _maxtasksperch_ für einen Multiprocess-Pool festlegen, der in meinem Fall dazu beigetragen hat, Ressourcen zu bereinigen, nachdem jeder Worker seine Arbeitslast beendet hat. [link] (https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool) – Kieleth

Verwandte Themen