Biuletyn nr 17

Biuletyn KDM
1 | 2 | 3 | 4 | 5
6 | 7 | 8 | 9 | 10
11 | 12 | 13 | 14
15 | 16 | 17 | 18
19 | 20 | 21 | 22
23 | 24 | 25 | 26
27 | 28 | 29 | 30
31 | 32
Lista biuletynów

Biuletyn nr 17 (7 grudnia 2006).

Spis treści

Programowanie: Listingi kompilatorów Cray

Autor: Łukasz Bolikowski

Kompilatory na tornado mają bardzo przydatną dla programistów opcję generowania raportów z kompilacji, tzw. listingów. Jest to informacja zwrotna od kompilatora, dzięki której można dowiedzieć się między innymi:

  • które pętle zostały zwektoryzowane
  • dlaczego inne pętle nie zostały zwektoryzowane
  • gdzie zastosowano inlining funkcji i procedur
  • króre nieużywane fragmenty kodu zostały usunięte
  • które fragementy kodu zostały zastąpione przez wywołania funkcji bibliotecznych
  • itp.

Listingi generowane są zarówno dla kodów w języku Fortran, jak i C.

Użycie

Podczas kompilacji pliku w Fortranie dodajemy następującą opcję:

 ftn -c -ra plik.f

Podczas kompilacji pliku w C dodajemy następującą opcję:

 cc -c -h list=a plik.c

W wyniku, oprócz pliku plik.o powstanie także plik plik.lst zawierający raport w najobszerniejszej postaci. Raport z kompilatora C jest niestety mniej obszerny niż w przypadku kompilatora Fortran, jednak wciąż zawiera wiele interesujących informacji.

Przykład: procedura VHPSI z pakietu PWscf

Ciekawym elementem listingu jest wyraźne zaznaczanie zasięgu pętli w kodzie, wraz zinformacjami o optymalizacjach dokonanych w odniesieniu do tych pętli (np. wektoryzacja, częściowa wektoryzacja, zrównoleglenie, rozwinięcie, połączenie z poprzednią pętlą, itp.) Po kompilacji z włączonym generowaniem listingu otrzymujemy między innymi następujące informacje.

  68.  1---------<   do ibnd = 1, mp
  69.  1 2-------<      do na = 1, nat
  70.  1 2                 nt = ityp (na)
  71.  1 2                 if (Hubbard_U(nt).ne.0.d0 .or. Hubbard_alpha(nt).ne.0.d0) then
  72.  1 2 3-----<            do m1 = 1, 2 * Hubbard_l(nt) + 1
  73.  1 2 3                     temp = proj (offset(na)+m1, ibnd)
  74.  1 2 3 MV--<               do m2 = 1, 2 * Hubbard_l(nt) + 1
  75.  1 2 3 MV                     temp = temp - 2.d0 * ns ( m1, m2, current_spin, na) * &
  76.  1 2 3 MV                                          proj (offset(na)+m2, ibnd)
  77.  1 2 3 MV-->               enddo
  78.  1 2 3
  79.  1 2 3                     temp = temp * Hubbard_U(nt)/2.d0
  80.  1 2 3                     temp = temp + proj(offset(na)+m1,ibnd) * Hubbard_alpha(nt)
  81.  1 2 3                     if (gamma_only) then
  82.  1 2 3                        call DAXPY (2*np, temp, swfcatom(1,offset(na)+m1), 1, &
  83.  1 2 3                                           hpsi(1,ibnd),              1)
  84.  1 2 3                     else
  85.  1 2 3                        call ZAXPY (np, temp, swfcatom(1,offset(na)+m1), 1, &
  86.  1 2 3                                           hpsi(1,ibnd),              1)
  87.  1 2 3                     endif
  88.  1 2 3----->            enddo
  89.  1 2                 endif
  90.  1 2------->      enddo
  91.  1--------->   enddo

W powyższym fragmencie widzimy zaznaczone poszczególne poziomy zagłębienia pętli, oraz informację o wektoryzacji i zrównolegleniu w obrębie multiprocesora MSP pętli w liniach 74-77.

ftn-6288 ftn: VECTOR File = vhpsi.f90, Line = 68
  A loop starting at line 68 was not vectorized because it contains a call to
  subroutine "daxpy" on line 82.

W powyższym fragmencie kompilator "tłumaczy się", dlaczego nie zwektoryzował pętli z linii 68.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                          O p t i o n s   R e p o r t
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Options :  -O scalar2,stream2,task1,vector2,fp2,modinline,ipa3,clone0,noaggress
           -O fusion2,infinitevl,nomsgs,negmsgs,nooverindex,pattern,recurrence
           -O shortcircuit3,unroll2,vsearch,nozeroinc
           -h nompmd
           -s default32
           -d acdhjlnovw0DILPQRS
           -e kmpqyBTZ
           -b vhpsi.o
           -f free
           -m3

Powyższy fragment informuje o wszystkich opcjach, jakie efektywnie zostały użyte w czasie kompilacji pliku (w tym przypadku w języku Fortran). Szczegółowe informacje o wszystkich opcjach kompilacji znajdują się na stronie manuala (polecenie man ftn)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                  C r o s s   R e f e r e n c e   for   VHPSI
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Name                             Storage                              Offset
----                             -------                              ------

COUNTER                          Stack
                                 INTEGER
  Decl      32
  Set       40      47
  Used      46      47      52

CURRENT_SPIN                     Use assoc
                                 INTEGER, From module LSDA_MOD
  Decl
  Set
  Used      21      75

W powyższym fragmencie widzimy początek indeksu wszystkich zmiennych występujących w kompilowanej procedurze. Przykładowo, dowiadujemy się, że zmienna COUNTER jest zdefiniowana w linii 32, ustawiana w liniach 40 i 47, a odczytywana w liniach 46, 47, 52. Natomiast zmienna CURRENT_SPIN jest wzięta z modułu LSDA_MOD i odczytywana w liniach 21 i 75 (to ostatnie wystąpienie jest również oznaczone kolorem zielonym w pierwszym cytowanym fragmencie).

Inne przykładowe fragmenty

  51.              sumc = sumd = 0.0;
  52.  ir------<   for (j = 0; j < n_; j++) {
  53.  ir MV---<     for (i = 0; i < m_; i++) {
  54.  ir MV           sumc += c0[i] * c0[i];
  55.  ir MV           sumd += d0[i] * d0[i];
  56.  ir MV--->     }
  57.  ir            c0 += ldc;
  58.  ir            d0 += ldd;
  59.  ir------>   }

W powyższym przykładzie w języku C, pętla rozpoczynająca się w linii 52 została zamieniona miejscami z pętlą w linii 53 (i = interchanged). Dodatkowo, pętla w linii 52 została rozwinięta (r = unrolled), a pętla w linii 53 została zwektoryzowana (V = vectorized) i zrównoleglona w obrębie multiprocesora MSP (M = multistreamed).

   7.  D--<       do i = 1, n
   8.  D            sumpi = 1.0*i
   9.  D-->       end do
  10.
  11.             sumpi = 0.0
  12.       !$omp parallel do default(none) private(i, x, mypart) shared(sumpi)
  13.  P--<       do i = 1, n
  14.  P            x = (i + 0.5) / n
  15.  P            mypart = 1.0/(1.0 + x**2)
  16.  P    !$omp critical
  17.  P            sumpi = sumpi + mypart
  18.  P    !$omp end critical
  19.  P-->       end do
  20.             sumpi = 4.0 * sumpi / n
ftn-6002 ftn: SCALAR File = test.f, Line = 7
  A loop starting at line 7 was eliminated by optimization.

Powyższy przykład pokazuje oznaczenie obszaru wykonywanego równolegle (P = parallel/tasked), a także usunięcie zbędnego fragmentu kodu (D = deleted) wraz z odpowiednim komunikatem kompilatora. Jeśli komunikat wydaje się być zbyt lakoniczny, można wydać polecenie

 explain ftn-6002

i uzyskać rozszerzone wyjaśnienie.

Podsumowanie

Generowanie listingu pomaga czytać kod, a także poznać i zrozumieć przyczyny pewnych decyzji optymalizacyjnych kompilatora. Jest to bardzo przydatna funkcja podczas samodzielnej optymalizacji kodu.

Fragmenty kodu wykorzystane w artykule pochodzą z programów: PWscf udostępnianego w licencji GNU, HPCC udostępnianego w licencji BSD, oraz własnych kodów udostępnianych jako public domain.




Narzędzia: Jak wstawiać filmy w LaTeX'owych slajdach Beamer?

Autor: Maciej Cytowski

Wstęp

Od pewnego czasu bardzo popularnym narzędziem przygotowywania prezentacji jest LaTeX'owy pakiet beamer. Powodów jest kilka. Po pierwsze bardzo ważna jest, znana z TeX'a, możliwość załączania równań matematycznych. Po drugie układ slajdów generowany jest automatycznie wedle wybranego przez nas szablonu. Po trzecie slajdy przygotowywane z użyciem beamer'a są zazwyczaj ładne i estetyczne. Ponad wszystkim jednak beamer jest darmowy, zarówno dla użytkowników systemów Linux, MacOS jak i Windows.

Wśród początkujących użytkowników beamer'a bardzo często pojawia się następujący problem: jak załączyć film do mojej prezentacji?

Artykuł ten opisuje dwa sposoby na rozwiązanie tego problemu.

Linki href

Pierwszym z opisanych sposobów jest utworzenie tzw. linka. Link to miejsce na slajdzie, które po kliknięciu uruchomia zewnętrzną aplikację. Ta metoda będzie zapewne częściej używana przez użytkowników systemu Linux, wobec czego opisane przykładowe aplikacje będą również typowymi aplikacjami Linux'owymi. Zobaczmy jak zrobić link krok po kroku:

Krok 1

Aby wczytać pakiet hyperref (pozwalający na tworzenie linków w LaTeX'u) umieszczamy następującą linię na początku LaTeX'ego źródła:

\usepackage{hyperref}

Krok 2

Aby utworzyć link należy wówczas wewnątrz, któregoś ze slajdów napisać:

\href{run:<showscript>}{ LINK }

gdzie LINK to coś do kliknięcia (może być to obrazek lub tekst), a <showscript> to ścieżka i nazwa programu używanego do uruchomienia, np. dla plików typu mpeg możemy użyć skryptu showscript.sh, który zawiera pojedynczą linię:

mplayer -geometry 30%:100% -nofs -xy 2 -loop 0  file.mpg

Skrypt taki może oczywiście wywoływać dowolną aplikację (może to być xine, totem, kaffeine lub inny dowolny program tego typu).

Krok 3

Możemy teraz wywołać komendę:

pdflatex movie.tex

Powinien zostać utworzony dokument movie.pdf, który zawierać będzie żądany link.

Przykład

Oto krótki przykład użycia link'ów:

\documentclass[11pt]{beamer}
\usetheme{Madrid}
\usepackage{hyperref}

\pgfdeclareimage[height=3cm]{link}{link} 

\title[]{How to use hyperref package?}
\author[HPC Newsletter]{HPC Newsletter}
\institute[ICM]{ICM}
\date[6 December 2006]{}

\begin{document}

\frame{
        \frametitle{Movies in beamer?}
        \begin{block}{It's easy!!!!!}
                \begin{center}
                \href{run:./showscript.sh}{ \pgfuseimage{link}}
                \end{center}
        \end{block}
}

\end{document}

Aby sprawdzić działanie powyższego przykładu można ściągnąć sobie następujące pliki:

Pakiet movie15

Drugim znanym mi sposobem na załączenie filmu w prezentacji beamer jest skorzystanie z pakietu movie15. Pakiet ten stworzony jest specjalnie do umieszczania filmów, dzwięków a nawet obiektów 3D w plikach o rozszerzeniu .pdf. Pakietu tego można używać również w zwykłych dokumentach LaTeX'owych.

Niestety użytkownicy systemów Linux będą mieli spory kłopot w odtwarzaniu napisanych za pomocą movie15 dokumentów zawierających filmy. Program acroread (jak również każdy inny program służący do przeglądania pdf-ów pod Linux'em) nie radzą sobie z plikami .pdf zawierającymi załączniki multimedialne. Wówczas nasz film będzie widziany jako link do filmu i po kliknięciu na niego zostanie otworzony standardowy odtwarzacz plików multilmedialnych w naszym systemie. Możemy natomiast tworzyć i kompilować prezentacje wykorzystujące movie15 za pomocą pdflatex. Pliki .pdf po przeniesieniu na system Windows lub MacOS powinny być odtwarzane już normalnie.

Jak wygląda użycie movie15:

Krok 1

Aby wczytać pakiet movie15 umieszczamy następującą linię na początku LaTeX'ego źródła:

\usepackage{movie15}

Krok 2

Aby wstawić film wewnątrz slajdu musimy wówczas skorzystać z polecenia o następującej składni:

\includemovie[<options>]{<width>}{<height>}{<media file>}

Argumenty <width> i <height> mogą pozostać puste, lub zdefiniowane przy pomocy standardowej składni LaTeX'a (np. .5\linewidth). <media file> specyfikuje nazwę pliku multimedialnego. Oto krótki opis niektórych opcji dostępnych do specyfikacji w ramach <options>:

  • attach[=false] - specyfikuje czy plik multimedialny ma być dołączony do dokumentu .pdf jako załącznik (domyślnie). Opcji attach=false właściwie nie powinniśmy używać, gdyż z jednej strony nie zabezpieczamy się przed ewentualnym wyciągnięciem plików z prezentacji (istnieją darmowe programy, które potrafią "wyciągać" pliki multimedialne z formatu .pdf), a po drugie tracimy możliwość oglądania filmów w niektórych Reader'ach.
  • autoplay - zacznij odtwarzać plik, gdy strona zostanie otwarta (przydatne gdy mamy kilka filmów i wszystkie mają być odtwarzane w tym samym czasie)
  • autopause - włącz automatyczną pauzę, gdy zamykamy stronę
  • autoclose - zamknij odtwarzacz, gdy zamykamy stronę
  • autostop - zatrzymaj odtwarzanie, ale nie wyłączaj odtwarzacza, gdy zamykamy stronę
  • autoresume - przywróć wcześniej wstrzymany pauzą fragment, gdy strona została otwarta ponownie
  • controls[=true|false] - podczas odtwarzania wyświetlane są paski kontroli związane z wybranym odtwarzaczem
  • externalviewer - otwiera i uruchamia film w zewnetrznym odtwarzaczu
  • label=<label spec> - skojarz plik multimedialny ze znacznikiem <label spec>. Poprzez taki znacznik możemy później odwoływać się do filmu w komendzie \movieref.
  • mouse[=true|false] - włącz interakcję myszy. Klikanie na film podczas odtwarzania pauzuje odtwarzanie, klikanie na zewnatrz wznawia.
  • poster - pokaż pierwszą klatkę filmu (lub początkową ustawioną przez startat), gdy film jest nieaktywny
  • poster=<image> - pokaż obraz określony ścieżką <image>, gdy film jest nieaktywny
  • startat=<offset> - określ początkowy moment filmu. <offset> może się odnosić do czasu, numeru klatki lub jakiegoś znacznika pliku, np: startat=time:20.5, startat=frame:100, startat={marker:"Chapter 1", time:60}
  • text=<text> - napis wyśrodkowany w oknie filmu (nie widoczny pod poster). Jeśli nasz Reader nie odczytuje plików multimedialnych zatopionych w dokumnetach typu .pdf ten napis jest wówczas zwykle linkiem do odtworzenia pliku zewnętrznym odtwarzaczem

Krok 3

Po przygotowaniu prezentacji należy skompilować ją za pomocą polecenia:

pdflatex movie2.tex

Powinien zostać utworzony plik movie2.pdf. Tak jak już było to wspomniane najlepiej jest oglądać go w Reader'ach w systemie Windows bądź MacOS.

Przykład

Oto krótki przykład użycia movie15:

\documentclass[11pt]{beamer}
\usetheme{Madrid}
\usepackage{movie15}

\pgfdeclareimage[height=3cm]{link}{link}

\title[]{How to use hyperref package?}
\author[HPC Newsletter]{HPC Newsletter}
\institute[ICM]{ICM}
\date[6 December 2006]{}

\begin{document}

\frame{
        \frametitle{Movies in beamer?}
        \begin{block}{It's easy!!!!!}
                \begin{center}
                \includemovie[poster,text=Easy example!,controls=true]{8cm}{6cm} {movie.mpg}
                \end{center}
                \smallskip
        \end{block}
}

\end{document}

Aby sprawdzić działanie powyższego przykładu można ściągnąć sobie następujące pliki:

Ciekawe strony




Programowanie: Programowanie równoległe w środowisku Matlab

Autor: Bartosz Borucki


Wstęp

Ze względu na wzrastające zainteresowanie obliczeniami w środowiskach rozproszonych oraz ze względu na ogromna popularnośc środowiska Matlab, również firma Mathworks umożliwiła użytkownikom Matlaba prowadzenie obliczeń równoległych i rozproszonych, poprzez wprowadzenie dwóch pakietów - Matlab Distributed Computing Engine oraz Distributed Computing Toolbox. Oba te pakiety rozszerzają możliwości Matlaba zapewniając obsługę programowania rozproszonego i równolełego, zarówno od strony technicznej (zarządzanie zadaniami, alokacja zasobów obliczeniowych, kolejkowanie), jak i programowej (funkcje umożliwiające użytkownikowi komunikację z zadaniami oraz programowanie zadań równoległych).

O ile programowanie rozproszone, polegające na uruchamianiu wielu niezależnych od siebie i niekomunikujących się zadań, jest tematem w miarę prostym i niewiele różniącym się od zwykłego programowania w środowisku Matlaba, o tyle programowanie równoległe, polegające na uruchamianiu zadania na wielu procesorach jednocześnie, jest problemem bardziej złożonym i wymagającym zarówno dokładnego przemyślenia algorytmu, jak i odpowiedniego zaprogramowania zadania. Ten artykuł ma na celu wyjaśnić pokrótce podstawy programowania zadań równoległych w Matlabie.


Matlab i systemy kolejkowe

Matlab Distributed Computing Engine wyposażony jest zarówno w wewnętrzny scheduler zarządzający zadaniami, jak również integruje się z istniejącymi systemami kolejkowymi takimi jak np. PBS. Ze względu na uniwersalnośc poniższego artykułu zakłada się, że jesteśmy w momencie, w którym mamy już przydzielone wymagane zasoby obliczeniowe, skonfigurowaną poprawnie komunikację, mamy uruchomioną obliczeniową sesję Matlba oraz wywołany scheduler równoległy (np. poprzez skrypt kolejkowy PBS oraz M-Plik uruchamiany z Matlabem). Szczegółowe polecenia oraz wytyczne dotyczące powyższych wymagań uzależnione są od danej konfiguracji maszyny obliczeniowej oraz ustaleń administratora.


Zadanie równoległe

Załóżmy, że sesja obliczeniowa Matlaba została uruchomiona w katalogu mdce/ i wywołany scheduler przypisany jest do zmiennej sched.

W celu utworzenia zadania równoległego (przypisanego do zmiennej pjob) należy wywołać następujące polecenie:

 pjob = createParallelJob(sched);

Do utworzonego zadania nalezy przypisać podzadanie (task), które będzie faktycznym programem obliczeniowym. W przypadku zadań równoległych w Matlabie, przypisujemy tylko jedno podzadanie, które następnie powielane jest na każdy przydzielony procesor. Cała konfiguracja podziału obliczeń na poszczególne procesory, cała komunikacja oraz wszystkie obliczenia muszą być zdefiniowane właśnie wewnątrz pojedynczego podzadania. Każdy pojedynczy proces obliczeniowy Matlaba pracujący na pojedynczym procesorze nazywany jest labem.

Podzadanie musi ponadto być napisane w postaci funkcji i zapisane w M-Pliku o nazwie jednakowej z nazwą funkcji, oraz umieszczone w katalogu mdce/, lub być wewnętrzną funkcją Matlaba. Funkcja będąca podzadaniem nie musi zwracać żadnych wartości ani przyjmować argumentów.

Do utworzenia podzadania służy funkcja createTask, która jako argumenty przyjmuje kolejno: obiekt zadania, wskaźnik do funkcji obliczeniowej, ilość argumentów wyjściowych funkcji obliczeniowej, argumenty wejściowe funkcji obliczeniowej.


Przykład 1: randn

Najprostszym możliwym zadaniem równoległym w Matlabie jest skorzystanie z wewnętrznej funkcji Matlaba. Dla przykładu weźmiemy funkcję randn, która zwraca macierz liczb losowych o rozkładzie normalnym, o zadanym rozmiarze i uruchomimy ją jednakowo, jednocześnie na wszystkich przydzielonych procesorach. Zatem musimy utworzyć podzadanie w zadaniu pjob, korzystające z funkcji randn, zwracające jedną wartość (macierz) i zadać argument będący rozmiarem generowanej macierzy. Wykonujemy to następującym poleceniem:

 createTask(pjob,@randn,1,{3});

gdzie argumentem wejściowym jest liczba 3.

Po uruchomieniu takiego zadania poleceniem:

 submit(pjob);

poczekaniu na zakończenie obliczeń:

 waitForState(pjob,'finished');

i pobraniu wyników do zmiennej results:

 results = getAllOutputarguments(pjob);

otrzymamy strukturę results zawierającą zwrócone przez kolejne procesory wyniki.

Ponieważ zadanie obliczeniowe pracuje zazwyczaj w nieinteraktywnej sesji Matlaba, po zakończeniu obliczeń należy uzyskane wyniki zapisać:

 save wyniki.mat results
 clear

Po ponownym wczytaniu wyników (np. w interfejsie graficznym Matlaba) możemy odwoływać się do kolejnych elementów struktury results za pomocą nawiasów klamrowych {}, a kolejne jej elementy będą wylosowanymi macierzami zwróconymi przez poszczególne laby:

>> results{1}
>> results{2}
...


Tak zdefiniowane zadnie równoległe pokazuje dobrze podstawową zasadę definiowania tego typu zadań, jednak ze strony obliczeń równoległych zdefiniowane jest nieoptymalnie. Funkcja obliczeniowa, pracująca na każdym procesorze, pobiera jeden liczbowy parametr, a zwraca całą wylosowaną macierz, która przy dużych wartościach parametru wejściowego może osiągnąć bardzo duże rozmiary. O ile duża ilość danych jest tylko argumentem wyjściowym całego podzadania, to problem rozmiaru danych nie jest bardzo znaczący - dane wyjściowe są bowiem przechowywane w plikach tymczasowych na współdzielonej przestrzeni dyskowej (z której uruchomiono obliczenia), a funkcja getAllOutputArguments jedynie pobiera te dane z dysku. Znacznie większym problemem byłoby przesłanie dużej ilości danych pomiędzy labami wewnątrz podzadania, co wymagałoby czasochłonnego przesyłania dużej ilości danych poprzez interfejsy komunikacyjne pomiędzy procesorami lub węzłami klastra.


Przykład 2: liczba Pi

Każdy kto zaczyna swoją przygodę z programowaniem równoległym musi trafić na najczęstszy przykład, zwany "Hello World" programowania równoległego, czyli równoległe obliczanie przybliżenia numerycznego liczby Pi. Zadanie to polega na numerycznym obliczeniu całki, której wynikiem jest właśnie liczba Pi. Obliczanie całek jest jednym z najlepiej zrównoleglających się zadań - przedział całkowania dzielony jest na fragmenty, każdy procesor oblicza całkę na jednym fragmencie przedziału, a następnie wyniki z poszczególnych procesorów są sumowane. Jak widać takie postawienie problemu, z punktu widzenia obliczeń równoległych, wymaga po pierwsze zdefiniowania podprzedziałów dla całek na poszczególnych procesorach, a po drugie wymaga przesłania poszczególnych wyników do jednego procesora i obliczenia sumy, będącej wynikiem całki.


Konieczne zatem jest napisanie funkcji, która nie pobierając żadnego argumentu zwróci wartość liczby Pi, obliczając ją poprzez całkowanie na dowolnej liczbie procesorów. Tworzymy zatem funkcję quadpi i zapisujemy ją w M-Pliku quadpi.m:

1:  function pi = quadpi()
2: 
3:  F = @(x)4./(1 + x.^2);
4:  a = (labindex - 1)/numlabs;
5:  b = labindex/numlabs;
6:
7:  myIntegral = quadl(F,a,b);
8:  if( labindex == 1)
9:  	pi = myIntegral;
10: 	for otherLab = 2:numlabs,
11: 		pi = pi + labReceive(otherLab);
12: 	end
13: else
14: 	pi = [];
15: 	labSend(myIntegral,1);
16: end 

i podobnie jak w poprzednim przykładzie (w skrypcie obliczeniowym Matlaba) tworzymy odpowiednie podzadanie, podając kolejno obiekt zadania (pjob), wskaźnik do funkcji (@quadpi), ilość zwracanych argumentów (1) oraz puste argumenty wejściowe ({}), a następnie uruchamiamy zadanie i po zakończeniu obliczeń zapisujemy wynik:

 createTask(pjob,@quadpi,1,{});
 submit(pjob);
 waitForState(pjob,'finished');
 results = getAllOutputarguments(pjob);
 pi = results{1};
 save pi.mat pi
 clear


Rozważmy zatem kolejno co robi powyższe zadanie i jak będą wyglądały czynności wykonywane przez poszczególne laby:

  • W pierwszej kolejności podzadanie zostanie powielone na tyle labów ile mamy przydzielonych procesorów (przypuśćmy, że są to 4 procesory) i rozesłane do obliczeń.
  • Na każdym labie uruchomi się funkcja quadpi.
  • Każdy lab zdefiniuje sobie funkcję całkowania F (linia 3).
  • Każdy lab obliczy sobie swój przedział całkowania [a,b] korzystając z funkcji labindex i numlabs. Funkcja labindex zwraca numer labu na którym jest uruchomiona dana kopia podzadania, natomiast funkcja numlabs zwraca całkowitą liczbę labów w zadaniu. A zatem dla 4 procesorów pierwszy lab ustali sobie przedział [0,0.25], drugi lab [0.25,0.5], trzeci lab [0.5,0.75], a czwarty lab [0.75,1].
  • Każdy lab obliczy całkę (funkcją quadl) na swoim przedziale [a,b] (linia 7).
  • Następnie w zależności od numeru labu:
    • laby które nie są labem 1, przypiszą wartość pustą do wyniku funkcji quadpi i wynik swojej całki wyślą do labu pierwszego przy użyciu funkcji labSend (linie 14 i 15)
    • lab pierwszy zsumuje swój wynik całki z wynikami odebranymi od innych labów funkcją labReceive i całość przypisze do wyniku funkcji quadpi.
  • Po zakończeniu obliczeń pobierane są wyniki (getAllOutputarguments), a wynik zwrócony przez pierwszy lab jest obliczoną wartością liczby Pi (pozostałe laby zwracają puste wartości).


Takie zadanie jest już nie tylko dobrym przykładem definiowania prostego zadania równoległego z podstawowoą komunikacją, ale również jest dobry przykład optymalnego wykorzystania programowania równoległego. Najbardziej czasochłonna część obliczeń - całka, została podzielona na poszczególne procesory, a komunikacją objęty jest jedynie łatwy do przesłania wynik całki, a zatem pojedyncza liczba rzeczywista. Ponadto zadanie zostało napisane w taki sposób, aby mogło być uruchamiane na dowolnej liczbie procesorów bez wprowadzania zmian w kodzie.


Przykład 3: komunikacja

Ostatnim przedstawionym w tym artykule przykładem będzie jedynie schemat rozwiązania większego problemu obliczeniowego. Celem tego przykładu jest przede wszystkim pokazanie jak wykorzystywać zewnętrzne pliki danych oraz korzystać z bardziej złożonej komunikacji.

Często spotykanym zadaniem obliczeniowym jest prowadzenie obliczeń na dużej macierzy liczb, w kolejnych krokach czasowych - tak, że każdy wartości w kolejnym kroku czasowym zależą od wartości z poprzedniego - np. w rozwiązywaniu równań różniczkowych. Tak zdefiniowany problem nie daje się zrównoleglić ze względu na główną pętlę czasową, ale często daje się podzielić na prowadzenie równoległych obliczeń na fragmentach macierzy plus niewielka komunikacja.

Dobrym przykładem może być tu na przykład rozwiązywanie podstawowego równania transportu ciepła (simple heat equation). Zadanie to obliczane na jednym procesorze wyglądałoby następująco:

  • definicja siatki obliczeniowej (macierzy wartości funkcji w węzłach)
  • definicja warunków początkowych
  • dla każdego kolejnego kroku czasowego, przebieg po wszystkich węzłach siatki (elementach macierzy) i obliczenie wartości w węzłach w kroku czasowym t na podstawie wartości z kroku t-1 i na podstawie wartości sąsiednich węzłów

To samo zadanie zdefiniowane równolegle wyglądałoby następująco:

  • definicja siatki obliczeniowej (macierzy wartości funkcji w węzłach)
  • definicja warunków początkowych
  • podział zakresów siatki na poszczególne procesory
  • na każdym procesorze dla każdego kolejnego kroku czasowego:
    • wysłanie wartości brzegowych do sąsiadów
    • odebranie wartości brzegowych od sąsiadów
    • przebieg po wszystkich węzłach siatki (dla danego procesora) i obliczenie wartości w węzłach w kroku czasowym t na podstawie wartości z kroku t-1 i na podstawie wartości sąsiednich węzłów
  • zebranie wyników w jedną macierz

Przesyłanie warunków brzegowych pomiędzy poszczególnymi procesorami jest w tym przykładzie konieczne, gdyż wartość węzła siatki zależy również od wartości węzłów sąsiednich, a zatem węzły brzegowe muszą korzystać z wartości obliczanych na innym procesorze.


Taki lub podobny schemat programu pojawia się często w zadaniach obliczeniowych. Jak widać z powyższego opisu z punktu widzenia programu, konieczne jest zapewnienie komunkiacji pomiędzy labami podczas prowadzenia obliczeń, a także konieczne jest zapewnienie synchronizacji czasowej pomiędzy poszczególnymi labami, tak aby nie musiały oczekiwać zbyt długo na wzajemne przesyłanie danych.

Przedstawiony poniżej schemat może zrealizować opisane wyżej zadanie dzieląc macierz obliczeniową pomiędzy poszczególne laby. Pokazuje również podstawowe zasady komunikacji w Matlabie oraz zasady korzystania z zewnętrznych plików danych:

1:  function out = nazwafunkcji(filename)
2:  
3:  if(labindex == 1)
4:  	load(filename);
5:  	%tu można wykonać preprocessing danych początkowych i zdefiniować macierz obliczeniową np. U
6: 	%tu oblicz zakres macierzy U i ustal macierz Ulab dla 1 labu
7:  	for n = 2:numlabs,
8:  		%tu ustal zakresy macierzy U dla labu 'n' uwzględniając że potrzeba również brzegi
9:   		labSend(U(zakresy),n);
10:	end
11: end
12: labBarrier;
13: if(labindex ~= 1)  
14: 	Ulab = labReceive(1); 
15: end
16: [X,Y] = size(Ulab); 	
17: T = 1000;	
18: for t=2:T,
19: 	if(labindex == 1)
20: 		labSend(moj_prawy_brzeg,2); 
21: 		brzeg_prawego_sasiada = labReceive(2);
22: 	elseif(labindex == numlabs)
23: 		labSend(moj_lewy_brzeg,numlabs-1);
24: 		brzeg_lewego_sasiada = labReceive(numlabs-1);
25: 	else
26: 		labSend(moj_lewy_brzeg,labindex-1);
27: 		labSend(moj_prawy_brzeg,labindex+1);
28: 		brzeg_lewego_sasiada = labReceive(labindex-1);
29: 		brzeg_prawego_sasiada = labReceive(labindex+1);
30: 	end
31: 	
32: 	for x=2:X-1,
33: 		for y=2:Y-1,
34: 			%oblicz wartość macierzy Ulab w kroku t
35: 		end
36: 	end
37: end
38:
39: if(labindex == 1)
40: 	for n=2:numlabs,
41: 		macierz_Ulab_od_labu_n = labReceive(n);
42: 		%tu poskładaj macierze Ulab w całość
43: 	end
44: 	out = U;
45: else
46: 	labSend(Ulab,1);
47: 	out = [];
48: end

Tak zdefiniowane zadanie podobnie jak w innych przykładach uruchamiamy po ustawieniu schedulera s i zadania równoleŋłego pjob:

 createTask(pjob,@nazwafunkcji,1,{'/home/user/dane.mat'});
 submit(pjob);
 waitForState(pjob,'finished');
 results = getAllOutputarguments(pjob);
 U = results{1};
 save output.mat U
 clear

Rozważmy zatem kolejno co dzieje się w poszczególnych fragmentach programu dla poszczególnych labów:

  • W liniach 4-10 pierwszy lab wczytuje plik z danymi wejściowymi, wykonuje preprocessing danych, ustala macierz obliczeniową oraz dzieli ją na fragmenty dla poszczególnych labów i wysyła odpowiedni fragment do odpowiedniego labu funkcją labSend.
  • W linii 12 polecenie labBarrier powoduje, że wszystkie laby czekają na moment dojścia do tej linii kodu. W tym przypadku pozostałe laby czekają aż pierwszy lab wykona swoje obliczenia i wyśle im odpowiednie dane.
  • Linia 14 to odbiór danych początkowych przez inne laby poleceniem labReceive.
  • W liniach 16 i 17 ustalane są zakresy pętli dla danego labu - kroki czasowe (jednakowe dla wsztskich) oraz rozmiary przydzielonych macierzy.
  • Od 19 do 36 linii znajduje się główna pętla czasowa:
    • jeżeli jesteśmy pierwszym labem wysyłamy swoje prawe wartości brzegowe (z kroku t-1) do labu 2 i odbieramy jedo lewe wartości brzegowe
    • jeżeli jesteśmy ostatnim labem wysyłamy swoje wartości brzegowe do labu poprzedniego i odbieramy jego prawe wartości brzegowe
    • jeżeli jesteśmy labem "w środku" to wysyłamy swoje prawe wartości brzegowe do laby następnego i lewe wrtości brzegowe do labu poprzedniego, oraz odbieramy od sąsiadów ich wartości brzegowe
    • obliczamy nowe wartości macierzy Ulab w danym kroku czasowym
  • na koniec, jeżeli jesteśmy labem 1 to odbieramy macierze od innych labów i składamu macierz U w całość, a jesłi jesteśmy labem innym to wysyłamy swoją macierz Ulab do 1 labu


Przedstawiony schemat powinien wystarczająco naszkicować zasady wykorzystywania komunikacji w MDCE, będąc jednocześnie dobrym przykładem zrównoleglenia zadania. Bardzo często spotykanym podejściem jest przeprowadzanie wstępnych obliczeń na jednym labie (preprocessing), a następnie rozesłanie odpowiednich fragmentów danych do innych labów. Możliwe jest również zapisanie danych na dysk i następnie odczytanie ich przez inne laby. Nie nadająca się do zrównoleglenia pętla czasowa musi wykonać się na każdym labie ale oblicza się jedynie na fragmencie macierzy, a dan brzegowe (konieczne do obliczeń na innych fragmentach) przesyłane są w każdym kroku do sąsiadów.

Podsumowanie

Matlab Distributed Computing Engine umożliwia zatem prowadzenie w pełni funkcjonalnych obliczeń równoległych w środowisku Matlaba, udostępniając zestaw funkcji koniecznych do poprawnego definiowania zadań i ułatwiających użytkownikowi zaprogramowanie zadania równoległego. Z punktu widzenia użytkownika, również wykorzystanie komunikacji pomiędzy poszczególnymi procesami obliczeniowymi oraz ich synchronizacji jest łatwe do zaprogramowania i daje pełną funkcjonalnośc (labSend, labReceive, labBroadcast, labBarrier).

Jeżeli zatem problem obliczeniowy daje się zrównoleglić, to korzystając z MDCE możemy skorzystać z poprawy wydajności zarówno od strony programowania równoległego, jak i od strony skuteczności Matlaba (wektoryzacja kodów) i jego prostoty programowania.


Życzenia

Autorzy: Redakcja

Z okazji zbliżających się świąt Bożego Narodzenia i nadchodzącego Nowego Roku, redakcja Biuletynu życzy czytelnikom dużo pomyślności, radości i spokoju, oraz pustych kolejek w systemie PBS.

Zachęcamy również wszystkich Państwa do udziału w Akcji Miś:

Akcja Miś