2013-06-11 13 views
20

Ostatnio zaobserwowałem dziwny efekt, gdy zmierzyłem wydajność mojej równoległej aplikacji przy użyciu modułu wieloprocesorowego i mpi4py jako narzędzi komunikacyjnych.Równoległe zastosowanie w Pythonie staje się znacznie wolniejsze w przypadku używania modułu mpi zamiast wieloprocesowego

Aplikacja wykonuje algorytmy ewolucyjne na zestawach danych. Większość operacji jest wykonywana sekwencyjnie, z wyjątkiem oceny. Po zastosowaniu wszystkich operatorów ewolucyjnych, wszystkie osoby muszą otrzymać nowe wartości sprawności, które są wykonywane podczas oceny. Zasadniczo jest to matematyczne obliczenie wykonywane na liście zmiennych (python). Przed oceną zestaw danych jest rozproszony przez rozproszenie mpi lub Pythona, następnie przechodzi do oceny równoległej, a później dane wracają poprzez mechanizm mpi's lub mechanizm Pool.map.

Moja platforma testowa to maszyna wirtualna (virtualbox) z systemem Ubuntu 11.10 z Open MPI 1.4.3 na Core i7 (4/8 rdzenie), 8 GB pamięci RAM i napęd SSD.

To, co uważam za naprawdę zaskakujące, to to, że uzyskuję dobre przyspieszenie, jednak w zależności od narzędzia komunikacji, po pewnym progu procesów, wydajność staje się gorsza. Można to zilustrować na poniższych zdjęciach.

oś Y - czas przetwarzania
oś x - Nr procesów
kolory - wielkość każdego (Nr pływaków)

1) z modułów w systemie wieloprocesorowym - Pool.map enter image description here

2) za pomocą MPI - Scatter/Zebrać enter image description here

3) Oba obrazy na górze każdego innego enter image description here

W pierwszej chwili myślałem, że to wina HyperThreading, bo za duże zestawy danych staje się wolniejszy po osiągnięciu 4 procesy fizyczne (4 rdzenie). Jednak powinno to być również widoczne w przypadku wieloprocesowości, a nie jest. Moim kolejnym przypuszczeniem jest to, że metody komunikacji mpi są znacznie mniej skuteczne niż metody Pythona, jednak trudno mi w to uwierzyć.

Czy ktoś ma jakieś wyjaśnienie tych wyników?

DODANO:

Zaczynam wierzyć, że to wina HyperThreading po wszystkim. Przetestowałem swój kod na maszynie z rdzeniem i5 (2/4 rdzenie), a wydajność jest gorsza w przypadku 3 lub więcej procesów. Jedyne wyjaśnienie, które przychodzi mi na myśl, to to, że i7, którego używam, nie ma wystarczającej ilości zasobów (pamięci podręcznej?), Aby obliczać ocenę równolegle z Hyperthreading i musi zaplanować więcej niż 4 procesy do uruchomienia na 4 fizycznych rdzeniach.

Jednak interesujące jest to, że kiedy używam mpi htop, pokazuje się pełne wykorzystanie wszystkich 8 rdzeni logicznych, co powinno sugerować, że powyższe stwierdzenie jest nieprawidłowe. Z drugiej strony, kiedy używam Pool.Map, nie wykorzystuje ona w pełni wszystkich rdzeni. Używa jednego lub dwóch do maksimum, a reszta tylko częściowo, znowu nie ma pojęcia, dlaczego zachowuje się w ten sposób. Jutro dołączę zrzut ekranu pokazujący to zachowanie.

Nie robię nic nadzwyczajnego w kodzie, to naprawdę proste (nie podaję całego kodu nie dlatego, że jest tajne, ale dlatego, że potrzebuje dodatkowych bibliotek, takich jak DEAP do zainstalowania .Jeśli ktoś jest naprawdę zainteresowany problem i gotowe do zainstalowania DEAP I mogę przygotować krótki przykład). Kod dla MPI jest nieco inny, ponieważ nie może zajmować się zbiornikiem populacji (który dziedziczy z listy). Oczywiście jest trochę na głowie, ale nic poważnego. Oprócz kodu, który pokazuję poniżej, reszta jest taka sama.

Pool.map:

def eval_population(func, pop): 
    for ind in pop: 
     ind.fitness.values = func(ind) 

    return pop 

# ... 
self.pool = Pool(8) 
# ... 

for iter_ in xrange(nr_of_generations): 
    # ... 
    self.pool.map(evaluate, pop) # evaluate is really an eval_population alias with a certain function assigned to its first argument. 
    # ... 

MPI - Rozproszenie/Zbierz

def divide_list(lst, n): 
    return [lst[i::n] for i in xrange(n)] 

def chain_list(lst): 
    return list(chain.from_iterable(lst)) 

def evaluate_individuals_in_groups(func, rank, individuals): 
    comm = MPI.COMM_WORLD 
    size = MPI.COMM_WORLD.Get_size() 

    packages = None 
    if not rank: 
     packages = divide_list(individuals, size) 

    ind_for_eval = comm.scatter(packages) 
    eval_population(func, ind_for_eval) 

    pop_with_fit = comm.gather(ind_for_eval) 

    if not rank: 
     pop_with_fit = chain_list(pop_with_fit) 
     for index, elem in enumerate(pop_with_fit): 
      individuals[index] = elem 

for iter_ in xrange(nr_of_generations): 
     # ... 
     evaluate_individuals_in_groups(self.func, self.rank, pop) 
     # ... 

dodano 2: Jak już wspomniano wcześniej zrobiłem kilka testów na moim komputerze i5 (2/4 rdzenie) i tutaj jest wynik: enter image description here

również znaleźć urządzenia 2 Xeony (2 x 6/12 rdzeni) i powtarza odniesienia: enter image description here

teraz mam 3 przykłady samym zachowanie. Kiedy przeprowadzam moje obliczenia w większej liczbie procesów niż fizyczne rdzenie, zaczyna się to pogarszać. Uważam, że dzieje się tak, ponieważ procesy na tym samym rdzeniu fizycznym nie mogą być wykonywane jednocześnie z powodu braku zasobów.

+1

Implementacje MPI zwykle mają wiele różnych algorytmów dla operacji zbiorowych takich jak rozproszenie i zbieranie. Większość bibliotek ma własną heurystykę, aby wybrać najlepszy algorytm, ale czasami kończy się niepowodzeniem. Niektóre biblioteki umożliwiają wymuszanie tych algorytmów, np. w Open MPI można to osiągnąć przekazując argumenty MCA do 'mpiexec'. Pomoże nam, jeśli powiesz nam, z jakiej implementacji MPI korzystasz. –

+0

Używam Open MPI, dodanego do pytania. – Michal

+0

Byłoby wspaniale, gdyby można było podać więcej kontekstu, na przykład pokazać części kodu, w którym dane są dystrybuowane (obie implementacje). Zwykle rozpraszają i zbierają w skali Open MPI bardzo dobrze z liczbą procesów, gdy wszystkie są uruchomione na jednym węźle. –

Odpowiedz

5

MPI jest rzeczywiście zaprojektowany do komunikacji między węzłami, więc rozmawiaj z innymi komputerami za pośrednictwem sieci. Użycie MPI w tym samym węźle może spowodować duży narzut dla każdej wiadomości, która ma zostać wysłana, w porównaniu do np. gwintowanie.

mpi4py tworzy kopię dla każdej wiadomości, ponieważ jest ukierunkowana na użycie pamięci rozproszonej. Jeśli twój OpenMPI nie jest skonfigurowany do użycia sharedmemory do komunikacji wewnątrz węzłów, ta wiadomość zostanie wysłana przez stos tcp kernela iz powrotem, aby dostarczyć go do drugiego procesu, który ponownie doda trochę narzutów.

Jeśli zamierzasz wykonywać obliczenia tylko na tym samym komputerze, nie ma potrzeby używania tutaj mpi.

Niektóre z nich omówiono in this thread.

Aktualizacja ipc-benchmark projekt stara się, by jakiś sens z jak różne rodzaje komunikacji wykonać na różnych systemach. (wielordzeniowy, wieloprocesorowy, pamięć współdzielona) A zwłaszcza jak wpływa to na wirtualne maszyny!

Zalecam uruchomienie testu porównawczego ipc na zwirtualizowanej maszynie i opublikowanie wyników. Jeśli wyglądają one podobnie do benchmarku this, można uzyskać duży wgląd w różnicę między tcp, gniazdami i rurami.

+0

Pracuję nad aplikacją do testowania algorytmów ewolucyjnych w różnych środowiskach komputerowych: komputerach, klastrach, siatce itd ... Nie kłócę się, czy jest to uzasadnione używanie MPI tutaj. Ciekawi mnie, skąd pochodzą koszty ogólne. A przy okazji Pythona używa pakietu do komunikacji, który jest zaimplementowany z rurami lub z gniazdami. Skonfigurowałem go do korzystania z gniazd, więc metoda komunikacji jest taka sama. – Michal

+0

@Michal to nie dlatego, że używasz gniazd, które używasz tcp. Oto kilka interesujących slajdów o różnych prędkościach, jakie można uzyskać za pomocą rur, gniazd lub shmem, + narzędzie do testowania wydajności, które powie, co może być najszybsze w twoim systemie (w zależności od tego, jak twoje rdzenie mogą się komunikować, w jaki sposób gniazda procesora mogą komunikować się i ich domena numa.) http://anil.recoil.org/talks/fosdem-io-2012.pdf –

+0

dzięki za wskazanie tego. Sprawdzę, co napisałeś i stronę, którą podałeś, kiedy mam trochę czasu. – Michal

Powiązane problemy