Istnieją dwie kluczowe różnice między imap
/ imap_unordered
i map
/ map_async
:
- Sposób, w jaki konsumują iterowalny, który im przekazujesz.
- Sposób, w jaki zwracają wynik.
map
zużywa twoją iterowalność, konwertując iterowalną na listę (zakładając, że nie jest to już lista), dzieląc ją na części i wysyłając te części do procesów roboczych w Pool
. Podział iteracji na części jest skuteczniejszy niż przekazywanie każdego elementu w iteracji między procesami jeden element na raz - szczególnie jeśli iteracja jest duża. Jednak przekształcenie iterowalnego w listę w celu podzielenia na fragmenty może mieć bardzo wysoki koszt pamięci, ponieważ cała lista będzie musiała być przechowywana w pamięci.
imap
nie zamienia iterowalnego kodu, który dajesz mu na listę, ani nie dzieli go na części (domyślnie). Będzie iterował po iterowalnym jednym elemencie na raz i wysyłał je do procesu roboczego. Oznacza to, że nie bierzesz pod uwagę pamięci przy konwertowaniu całej iterowalności na listę, ale oznacza to również, że wydajność jest mniejsza w przypadku dużych iteracji z powodu braku porcji. Można to jednak złagodzić, przekazując chunksize
argument większy niż domyślny 1.
Inną ważną różnicą między imap
/ imap_unordered
i map
/ map_async
jest to, że za pomocą imap
/ imap_unordered
możesz zacząć otrzymywać wyniki od pracowników, gdy tylko będą gotowi, zamiast czekać na ich ukończenie. Za pomocą map_async
, AsyncResult
zwracane jest od razu, ale nie można faktycznie pobrać wyników z tego obiektu, dopóki wszystkie nie zostaną przetworzone, w którym to momencie zwraca tę samą listę, co map
robi ( map
jest faktycznie zaimplementowana wewnętrznie jako map_async(...).get()
). Nie ma sposobu, aby uzyskać częściowe wyniki; albo masz cały wynik, albo nic.
imap
i imap_unordered
oba zwracają iterable od razu. Dzięki imap
, wyniki zostaną wygenerowane z iterowalnego, gdy tylko będą gotowe, przy jednoczesnym zachowaniu kolejności iterowalnej wejściowej. Dzięki imap_unordered
wyniki zostaną wyświetlone, gdy tylko będą gotowe, niezależnie od kolejności iteracji danych wejściowych. Powiedzmy, że masz:
import multiprocessing
import time
def func(x):
time.sleep(x)
return x + 2
if __name__ == "__main__":
p = multiprocessing.Pool()
start = time.time()
for x in p.imap(func, [1,5,3]):
print("{} (Time elapsed: {}s)".format(x, int(time.time() - start)))
Spowoduje to:
3 (Time elapsed: 1s)
7 (Time elapsed: 5s)
5 (Time elapsed: 5s)
Jeśli użyjesz p.imap_unordered
zamiast p.imap
, zobaczysz:
3 (Time elapsed: 1s)
5 (Time elapsed: 3s)
7 (Time elapsed: 5s)
Jeśli użyjesz p.map
lub p.map_async().get()
, zobaczysz:
3 (Time elapsed: 5s)
7 (Time elapsed: 5s)
5 (Time elapsed: 5s)
Tak więc główne powody używania imap
/ imap_unordered
przerzucenia map_async
to:
- Twoja iterowalność jest na tyle duża, że przekonwertowanie jej na listę spowoduje, że zabraknie Ci pamięci / zużyjesz za dużo pamięci.
- Chcesz móc rozpocząć przetwarzanie wyników, zanim wszystkie zostaną zakończone.
apply
wysyła pojedyncze zadanie do procesu roboczego , a następnie blokuje, dopóki nie zostanie ukończone.apply_async
wysyła pojedyncze zadanie do procesu roboczego, a następnie natychmiast zwracaAsyncResult
obiekt, którego można użyć do oczekiwania na zakończenie zadania i odzyskania wyniku.apply
jest realizowane przez zwykłe wywołanieapply_async(...).get()
Pool
dokumentacji, a nie w tej nudnej .