Dlaczego moje skalowanie mnożenia macierzy-wektora?


15

Przepraszam za długi post, ale chciałem załączyć wszystko, co uważałem za istotne za pierwszym razem.

Czego chcę

Wdrażam równoległą wersję Krystalicznych metod podprzestrzeni dla gęstych matryc. Głównie GMRES, QMR i CG. Zdałem sobie sprawę (po profilowaniu), że moja procedura DGEMV była żałosna. Postanowiłem więc skoncentrować się na tym, izolując to. Próbowałem uruchomić go na 12-rdzeniowym komputerze, ale poniższe wyniki dotyczą 4-rdzeniowego laptopa Intel i3. Trend nie ma dużej różnicy.

Moje KMP_AFFINITY=VERBOSEwyniki są dostępne tutaj .

Napisałem mały kod:

size_N = 15000
A = randomly_generated_dense_matrix(size_N,size_N); %Condition Number is not bad
b = randomly_generated_dense_vector(size_N);
for it=1:n_times %n_times I kept at 50 
 x = Matrix_Vector_Multi(A,b);
end

Wierzę, że to symuluje zachowanie CG przez 50 iteracji.

Co próbowałem:

Tłumaczenie

Pierwotnie napisałem kod w Fortranie. Przetłumaczyłem to na C, MATLAB i Python (Numpy). Nie trzeba dodawać, że MATLAB i Python byli okropni. Nieoczekiwanie, C było lepsze od FORTRAN o sekundę lub dwie dla powyższych wartości. Konsekwentnie

Profilowy

Wyprofilowałem mój kod, aby działał i działał przez 46.075kilka sekund. To było, gdy MKL_DYNAMIC był ustawiony naFALSE i wszystkie rdzenie były używane. Jeśli jako prawdę użyłem MKL_DYNAMIC, tylko (około) połowa rdzeni była używana w danym momencie. Oto kilka szczegółów:

Address Line    Assembly                CPU Time

0x5cb51c        mulpd %xmm9, %xmm14     36.591s

Najbardziej czasochłonnym procesem wydaje się być:

Call Stack                          LAX16_N4_Loop_M16gas_1
CPU Time by Utilization             157.926s
CPU Time:Total by Utilization       94.1%
Overhead Time                       0us
Overhead Time:Total                 0.0%    
Module                              libmkl_mc3.so   

Oto kilka zdjęć:wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Wnioski:

Jestem prawdziwym początkującym w profilowaniu, ale zdaję sobie sprawę, że przyspieszenie wciąż nie jest dobre. Kod sekwencyjny (1 rdzeń) kończy się w 53 sekundy. To przyspieszenie mniejsze niż 1,1!

Prawdziwe pytanie: Co powinienem zrobić, aby poprawić moje przyspieszenie?

Rzeczy, które moim zdaniem mogą pomóc, ale nie jestem pewien:

  • Implementacja Pthreads
  • Implementacja MPI (ScaLapack)
  • Strojenie ręczne (nie wiem jak. Proszę polecić zasób, jeśli to zasugerujesz)

Jeśli ktoś potrzebuje więcej (szczególnie dotyczących pamięci) szczegółów, daj mi znać, co powinienem uruchomić i jak. Nigdy wcześniej nie profilowałem pamięci.

Odpowiedzi:


20

Twoja matryca ma rozmiar 15 000 x 15 000, więc masz w niej 225 mln elementów. To daje około 2 GB pamięci. Jest to znacznie więcej niż wielkość pamięci podręcznej procesora, dlatego należy ją ładować całkowicie z pamięci głównej przy każdym pomnożeniu macierzy, co zapewnia około 100 GB transferu danych oraz to, czego potrzebujesz dla wektorów źródłowych i docelowych.

Maksymalna przepustowość pamięci w i3 wynosi około 21 GB / s w oparciu o specyfikacje Intela, ale jeśli spojrzysz w Internecie, przekonasz się, że co najwyżej połowa z nich jest naprawdę dostępna. Zatem przynajmniej można oczekiwać, że test będzie trwał 10 sekund, a faktyczny pomiar 45 sekund nie jest tak daleko od tego znaku.

Jednocześnie wykonujesz około 10 miliardów mnożników zmiennoprzecinkowych i dodaje. Biorąc pod uwagę, powiedzmy, 10 cykli zegara dla kombinacji i częstotliwość taktowania 3 GHz, wyjdziesz na około 30 sekund. Oczywiście mogą działać równolegle z spekulacyjnymi ładowaniami pamięci, jeśli pamięć podręczna jest sprytna.

Podsumowując, powiedziałbym, że nie jesteś zbyt daleko od celu. Czego byś się spodziewał?


Czy nie ma sposobu na przynajmniej przyspieszenie 2-3?
Zapytanie

@Nunoxic - Możesz sprawdzić wydajność pamięci w swoim systemie za pomocą narzędzia takiego jak SiSoftware Sandra. Analiza Wolfganga wydaje mi się trafna, jeśli twoja aplikacja jest związana z przepustowością pamięci, równoległość niewiele pomoże, jeśli w ogóle. Spójrz także na wszystkie opcje oszczędzania energii, które mogą mieć, mogą one dławić wydajność pamięci. Weź również pod uwagę zamianę pamięci na pamięć o wyższej jakości, na przykład niższe opóźnienie CAS może mieć duży wpływ na czas spędzany na ścianie.
Mark Booth

4

Jak sobie radzisz mnożąc wektor macierzowy? Podwójna pętla ręcznie? A może połączenia z BLAS? Jeśli używasz MKL, zdecydowanie polecam korzystanie z procedur BLAS w wersji z wątkami.

Z ciekawości możesz również skompilować własną zestrojoną wersję ATLAS- a i zobaczyć, jak to działa na twój problem.

Aktualizacja

Po dyskusji w komentarzach poniżej okazuje się, że twój Intel Core i3-330M ma tylko dwa „prawdziwe” rdzenie. Dwa brakujące rdzenie są emulowane za pomocą hiperwątkowania . Ponieważ w rdzeniach hiperwątkowych zarówno magistrala pamięci, jak i jednostki zmiennoprzecinkowe są wspólne, nie uzyskasz żadnego przyspieszenia, jeśli którykolwiek z tych dwóch czynników będzie czynnikiem ograniczającym. W rzeczywistości użycie czterech rdzeni prawdopodobnie nawet spowolni sytuację.

Jakie wyniki uzyskujesz na „tylko” dwóch rdzeniach?


Próbowałem ATLA, GoTo i Netlib BLAS. Wszystkie są słabsze niż MKL pod względem wydajności. Czy jest to oczekiwane, czy robię coś złego? Skompilowałem ATLAS, jak wspomniano w podręczniku. Ponadto, mam wklejony moje (dokładne) kod tutaj . Nazywa się BLAS MKL.
Inquest

Ok, a jeśli chodzi o skalowanie, czy jesteś pewien, że w podstawowym przypadku kod działa tylko na jednym procesorze? Np. Jeśli go przetestujesz, to czy histogram użycia procesora pokazuje tylko jeden rdzeń?
Pedro

Tak. Histogram procesora pokazuje 1 rdzeń.
Zapytanie

Jeszcze raz z ciekawości, co dostajesz za dwa lub trzy rdzenie? Czy twoja maszyna faktycznie ma cztery rdzenie fizyczne, czy tylko dwa rdzenie z hiperwątkiem ?
Pedro

Jak się tego dowiem? Uwzględniłem moją KMP_AFFINITY w głównej części.
Inquest

0

Mam wrażenie, że porządkowanie według rzędów jest optymalne dla tego problemu pod względem czasu dostępu do pamięci, użycia linii pamięci podręcznej i braków TLB. Wydaje mi się, że twoja wersja FORTRAN używała zamiast tego porządkowania kolumn, co może wyjaśniać, dlaczego jest konsekwentnie wolniejsze niż wersja C.

b nie jest przechowywany w pamięci podręcznej. Możesz sprawdzić, czy obserwujesz taką samą (efektywną) przepustowość pamięci dla rozmiaru_N = 15000 niż dla wielkości_N = 5000. Jeśli to zrobisz, istnieje spora szansa, że ​​kod jest już optymalny i że przepustowość pamięci twojego systemu jest po prostu nie tak wspaniale.

Możesz także przetestować prędkość, jeśli po prostu zsumujesz wszystkie elementy macierzy w jednej pętli zamiast mnożenia wektora macierzy. (Możesz rozwinąć pętlę o współczynnik 4, ponieważ brak asocjatywności dodawania może uniemożliwić kompilatorowi wykonanie tej optymalizacji.)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.