Załóżmy, że mam dużą tablicę numpy w pamięci, mam funkcję, func
która przyjmuje tę gigantyczną tablicę jako dane wejściowe (wraz z kilkoma innymi parametrami). func
z różnymi parametrami mogą działać równolegle. Na przykład:
def func(arr, param):
# do stuff to arr, param
# build array arr
pool = Pool(processes = 6)
results = [pool.apply_async(func, [arr, param]) for param in all_params]
output = [res.get() for res in results]
Jeśli użyję biblioteki wieloprocesorowej, ta olbrzymia tablica zostanie skopiowana wiele razy do różnych procesów.
Czy istnieje sposób, aby różne procesy korzystały z tej samej tablicy? Ten obiekt tablicy jest tylko do odczytu i nigdy nie będzie modyfikowany.
Co jest bardziej skomplikowane, jeśli arr nie jest tablicą, ale dowolnym obiektem w języku Python, czy istnieje sposób na udostępnienie go?
[ZMIENIONO]
Przeczytałem odpowiedź, ale nadal jestem trochę zdezorientowany. Ponieważ fork () jest funkcją kopiowania przy zapisie, nie powinniśmy wywoływać żadnych dodatkowych kosztów podczas tworzenia nowych procesów w wieloprocesorowej bibliotece Pythona. Ale poniższy kod sugeruje, że istnieje ogromny narzut:
from multiprocessing import Pool, Manager
import numpy as np;
import time
def f(arr):
return len(arr)
t = time.time()
arr = np.arange(10000000)
print "construct array = ", time.time() - t;
pool = Pool(processes = 6)
t = time.time()
res = pool.apply_async(f, [arr,])
res.get()
print "multiprocessing overhead = ", time.time() - t;
wyjście (a przy okazji, koszt rośnie wraz ze wzrostem rozmiaru tablicy, więc podejrzewam, że nadal istnieje narzut związany z kopiowaniem pamięci):
construct array = 0.0178790092468
multiprocessing overhead = 0.252444982529
Dlaczego jest tak duże obciążenie, jeśli nie skopiowaliśmy tablicy? A jaką część oszczędza mi pamięć współdzielona?
Odpowiedzi:
Jeśli używasz systemu operacyjnego, który używa
fork()
semantyki kopiowania przy zapisie (jak każdy zwykły unix), to dopóki nie zmienisz struktury danych, będzie on dostępny dla wszystkich procesów potomnych bez zajmowania dodatkowej pamięci. Nie będziesz musiał robić nic specjalnego (z wyjątkiem absolutnej pewności, że nie zmieniasz obiektu).Najbardziej wydajną rzeczą , jaką możesz zrobić dla swojego problemu, byłoby spakowanie tablicy w wydajną strukturę tablicową (przy użyciu
numpy
lubarray
), umieszczenie jej w pamięci współdzielonej, zawinięcie jejmultiprocessing.Array
i przekazanie jej do funkcji. Ta odpowiedź pokazuje, jak to zrobić .Jeśli chcesz udostępnić obiekt z możliwością zapisu , musisz go otoczyć jakąś synchronizacją lub blokowaniem.
multiprocessing
udostępnia dwie metody, aby to zrobić : jedną z wykorzystaniem pamięci współdzielonej (odpowiedniej dla prostych wartości, tablic lub typów ctypów) lubManager
proxy, gdzie jeden proces przechowuje pamięć, a menedżer decyduje o dostępie do niej z innych procesów (nawet przez sieć).Manager
Podejście może być wykorzystywane z dowolną Pythona obiektów, ale będzie mniejsza niż równoważne im wspólnej pamięci, ponieważ przedmioty muszą być serializowane / rozszeregować i wysyłane tylko procesów.W Pythonie dostępnych jest wiele bibliotek przetwarzania równoległego i metod .
multiprocessing
to doskonała i wszechstronna biblioteka, ale jeśli masz specjalne potrzeby, być może jedno z pozostałych podejść może być lepsze.źródło
apply_async
powinna odwoływać się do współdzielonego obiektu bezpośrednio w zakresie zamiast poprzez swoje argumenty.Napotkałem ten sam problem i napisałem małą klasę narzędzi pamięci współdzielonej, aby go obejść.
Używam
multiprocessing.RawArray
(bez blokady), a także dostęp do tablic nie jest w ogóle zsynchronizowany (bez blokady), uważaj, aby nie strzelić sobie nogami.Dzięki temu rozwiązaniu uzyskuję około 3-krotne przyspieszenie na czterordzeniowym i7.
Oto kod: Zapraszam do korzystania i ulepszania go oraz zgłaszania wszelkich błędów.
źródło
To jest zamierzony przypadek użycia Ray , który jest biblioteką równoległego i rozproszonego Pythona. Pod maską serializuje obiekty przy użyciu układu danych Apache Arrow (który jest formatem zerowej kopii) i przechowuje je w magazynie obiektów pamięci współdzielonej, dzięki czemu można uzyskać do nich dostęp przez wiele procesów bez tworzenia kopii.
Kod wyglądałby następująco.
Jeśli nie wywołasz,
ray.put
tablica nadal będzie przechowywana w pamięci współdzielonej, ale zostanie to zrobione raz na wywołaniefunc
, co nie jest tym, czego chcesz.Zauważ, że zadziała to nie tylko w przypadku tablic, ale także w przypadku obiektów, które zawierają tablice , np. Słowniki mapujące ints do tablic, jak poniżej.
Możesz porównać wydajność serializacji w Ray i pickle, uruchamiając następujące polecenie w IPython.
Serializacja z Rayem jest tylko trochę szybsza niż pikle, ale deserializacja jest 1000x szybsza ze względu na użycie pamięci współdzielonej (ta liczba będzie oczywiście zależała od obiektu).
Zobacz dokumentację Ray . Możesz przeczytać więcej o szybkiej serializacji za pomocą Ray and Arrow . Uwaga Jestem jednym z programistów Ray.
źródło
Jak wspomniał Robert Nishihara, Apache Arrow sprawia, że jest to łatwe, szczególnie dzięki przechowywaniu obiektów w pamięci Plazmy, na którym zbudowany jest Ray.
Stworzyłem plazmę mózgową specjalnie z tego powodu - szybkie ładowanie i przeładowywanie dużych obiektów w aplikacji Flask. Jest to przestrzeń nazw obiektów pamięci współużytkowanej dla obiektów
pickle
możliwych do serializacji Apache Arrow, w tym 'd bytestring'ów generowanych przezpickle.dumps(...)
.Kluczową różnicą w stosunku do Apache Ray i Plasma jest to, że śledzi identyfikatory obiektów za Ciebie. Wszystkie procesy, wątki lub programy działające lokalnie mogą współużytkować wartości zmiennych, wywołując nazwę z dowolnego
Brain
obiektu.źródło