R-24-07.doc

(449 KB) Pobierz
PLP_Rozdział_24

Rozdział 24. Klastry Beowulf

W ciągu ostatniego dziesięciolecia nastąpił olbrzymi wzrost wydajności i szybki spadek cen komputerów osobistych oraz sprzętu sieciowego. Użytkownicy komputerów zetknęli się także z bezpłatnie dostępnym oprogramowaniem systemowym o wysokiej jakości, przeznaczonym dla takich komputerów. Kody źródłowe różnych pakietów oprogramowania zostały także udostępnione publicznie, co pozwala na ich ulepszanie i modyfikacje. W roku 1994. agencja NASA uruchomiła dla własnych celów projekt budowy komputera równoległego, który miał wykorzystywać powszechnie dostępne elementy i bezpłatnie rozpowszechniane pakiety oprogramowania. Komputer, któremu nadano nazwę Beowulf, został zbudowany z 16 jednostek z procesorami typu x86 firmy Intel, połączonych siecią Ethernet o przepływności 10 Mb/s. Był wyposażony w system operacyjny Linux oraz inne swobodnie dostępne programy rozpowszechniane zgodnie z zasadami licencji GPL. W ostatnich latach takie klastry komputerów stały się bardzo popularne ze względu na swoją niską cenę, dobrą wydajność i wysoką pewność działania.

W tym rozdziale zapoznamy się z architekturą, konfiguracją oprogramowania oraz programowaniem klastrów Beowulf. Główny nacisk kładziemy tu na aspekty programowania, pokazując kilka programów przeznaczonych do eksperymentów z klastrami. Programy te mogą działać zarówno na pojedynczym komputerze, jak i na wielu komputerach tworzących klaster.

Konfiguracja sprzętowa

Klaster Beowulf składa się z zestawu komputerów połączonych poprzez sieć i tworzących system ze wspólną (ang. shared) lub ściśle powiązaną pamięcią (ang. tightly coupled memory). Na poniższym schemacie pokazano typową konfigurację takiego klastra. Elementy n0 ... n7 oznaczają komputery, zaś element S jest przełącznikiem lub hubem sieciowym.

Ze względu na niską cenę i stosunkowo wysoką wydajność, popularność wśród użytkowników uzyskały komputery z procesorami Pentium firmy Intel. Jako osprzęt sieciowy stosowane są różne elementy: poczynając od prostych hubów Ethernet 10 Mbit/s aż do przełączników Myrinet (niezbyt drogie, wydajne przełączniki pakietów opracowane przez firmę Myrinet) lub najwydajniejszych gigabitowych przełączników Ethernet. System z hubem Ethernet 10 Mbit/s nadaje się do zastosowań domowych dla tych użytkowników, którzy chcą się czegoś nauczyć i poeksperymentować z klastrami Beowulf. Takie klastry nadają się także do uruchamiania programów działających równolegle, o niewielkim zapotrzebowaniu na komunikowanie się procesorów ze sobą podczas obliczeń. Dobrym rozwiązaniem dla małej firmy lub instytucji badawczej o niewielkim budżecie jest system wykorzystujący przełącznik Ethernet 100 Mbit/s. Obecnie dostępne są takie 64-portowe przełączniki umożliwiające połączenie 64 jednostek. Większe systemy można tworzyć, łącząc kilka przełączników kaskadowo. Można również zaopatrzyć się w przełączniki o większej liczbie portów, które ostatnio pojawiają się na rynku. Sieci o najwyższej wydajności, w których zastosowano rozwiązania firmy Myrinet lub gigabitowy Ethernet są używane głównie przez agencje rządowe — tutaj klastry Beowulf są alternatywą dla tradycyjnych superkomputerów. Przykładem takiego rozwiązania jest Centrum Lotów Kosmicznych Goddarda zarządzane przez NASA, w którym działa system 200 procesorów połączonych szybką siecią Ethernet i Myrinet. Systemy wykorzystujące Myrinet są około dziesięciokrotnie szybsze niż systemy Ethernet 100 Mbit/s.

Konfiguracja oprogramowania

Jak już wspomniano wcześniej, klaster Beowulf korzysta z pakietów ogólnie dostępnego bezpłatnego oprogramowania, rozpowszechnianych na zasadach licencji GPL. Powszechnie używanym systemem operacyjnym jest w nich Linux, ponieważ można w nim łatwo skonfigurować klaster Beowulf. W tym rozdziale zakładamy, że Czytelnik jest zaznajomiony z instalacją systemu Linux na komputerze osobistym. Jeśli klaster Beowulf zawiera tylko kilka węzłów, to można je konfigurować kolejno z tej samej płyty CD-ROM. Trzeba przy tym pamiętać o następujących zagadnieniach:

q       Jeden z węzłów klastra jest konfigurowany jako nadrzędny (ang. master), a pozostałe jako podrzędne (ang. slaves).

q       Węzeł nadrzędny i węzły podrzędne są połączone ze sobą za pomocą sieci. Oprócz tego węzeł nadrzędny ma zwykle dostęp do sieci zewnętrznej za pomocą łącza Ethernet lub modemu. Dlatego właśnie ważny jest wybór odpowiedniej obsługi sieci i modułów ze sterownikami kart sieciowych — ponieważ podczas konfiguracji węzłów potrzebna będzie działająca sieć.

q       Wygodnie jest skonfigurować konta każdego użytkownika tak, aby wszystkie węzły korzystały ze wspólnego katalogu macierzystego (/home). Katalog ten zazwyczaj umieszcza się na komputerze nadrzędnym i eksportuje do pozostałych węzłów klastra. Jeżeli Czytelnik nie wie, jak eksportować i montować katalogi za pomocą NFS, powinien zastosować procedurę opisaną w rozdziale 22., którą w skrócie podajemy niżej:

Najpierw należy utworzyć odpowiednie wpisy w pliku /etc/export w węźle nadrzędnym, podając, które katalogi mają być udostępnione. Należy tam wstawić wpis /home rw, dzięki czemu katalog ten będzie dostępny do zapisu. Format pliku jest podobny do formatu używanego w /etc/fstab. Następnie w każdym węźle należy udostępnić każdy napęd w pliku /etc/fstab jako np. master:/home /home.

q       Ponieważ węzły tworzą silnie sprzężony klaster, każdy użytkownik musi mieć udostępnione bez hasła programy rsh, rcp oraz rlogin na wszystkich węzłach podrzędnych (włącznie z użytkownikiem root). Dzięki temu uzyskujemy system działający jak jeden komputer z dostępem z jednego miejsca, czyli przez węzeł nadrzędny. Jedynie ten węzeł musi być w pełni zabezpieczony, ponieważ tylko on komunikuje się ze światem zewnętrznym. Należy się upewnić, czy każdy plik /etc/hosts.equiv lub $HOME/.rhosts zawiera wpisy dotyczące wszystkich komputerów tworzących klaster (łącznie z nazwami lokalnymi i nazwą węzła nadrzędnego). Taki plik można udostępnić na wspólnym dysku.

Programowanie klastra Beowulf

W klastrach Beowulf stosuje się model programowania polegający na przekazywaniu komunikatów (ang. message-passing programming model). Oznacza to, że program działający równolegle składa się z procesów, z których każdy przetwarza własny podzbiór danych. W celu uzyskania dostępu i modyfikacji „obcych” danych procesy porozumiewają się ze sobą, wymieniając komunikaty. Najbardziej znaną biblioteką obsługującą przekazywanie komunikatów jest Message Passing Interface (w skrócie MPI). Została ona opracowana przez forum MPI — czyli konsorcjum utworzone przez uniwersytety, agencje rządowe i instytucje badawcze. Standard MPI jest wykorzystywany w kilku pakietach programowania. Istnieje także inna biblioteka wykorzystująca wymianę komunikatów o nazwie Parallel Virtual Machine (w skrócie PVM), opracowana w Oakridge National Laboratory, lecz nią zajmiemy się później.

Programowanie z wykorzystaniem MPI

W tym podrozdziale zajmiemy się pakietem oprogramowania MPICH dostępnym bezpłatnie na stronie Argonne National Laboratory (http://www-unix.mcs.anl.gov/mpi/mpich/index.html). Pierwszym krokiem przed programowaniem klastra Beowulf powinno być połączenie się z tą stroną i pobranie odpowiedniego pakietu. Pakiet MPICH zawiera także podręcznik systemowy i podręcznik użytkownika, które można pobrać z tej samej strony. Materiały te wyjaśniają dokładnie proces instalacji pakietu MPICH i wywołania biblioteki MPI. Po pobraniu skompresowanego archiwum mpich.tar.gz na komputer główny (np. n0) procedura instalacji wygląda następująco:

  1. Zalogować się jako root.
  2. Rozkompresować i rozpakować pobrany pakiet.
  3. Przejść do katalogu mpich.
  4. W celu wybrania domyślnej architektury uruchomić skrypt ./configure.

Jeżeli mamy klaster SMP z węzłami wieloprocesorowymi, to wspomaganie architektury SMP w MPICH włącza się następująco:

 

# ./configure -opt=-O-comm shared

W takim przypadku MPICH może korzystać ze wspólnej pamięci dla komunikacji wewnątrzwęzłowej (ang. intra-node) oraz z TCP/IP dla komunikacji międzywęzłowej (ang. inter-node) między procesorami.

  1. Skompilować oprogramowanie:

 

# make > make.log 2>&1

  1. Sprawdzić w pliku make.log, czy nie wystąpiły błędy.
  2. Jeżeli kompilacja odbyła się bez błędów, zainstalować oprogramowanie:

 

# make PREFIX=/usr/local/mpi/install

  1. Utworzyć lub zmodyfikować plik /usr/local/mpi/util/machines/machines.LINUX, dodając nazwy węzłów. W naszym przykładowym klastrze zawartość tego pliku wygląda następująco:

 

n1

n2

n3

n4

n5

n6

n7

Format tego pliku jest podobny do formatu używanego w pliku .rhosts — jeden wpis dotyczy jednego węzła. Węzeł główny (np. n0), na którym jest uruchamiany program MPI, nie jest wpisywany do pliku machines.LINUX, ponieważ MPI zawsze uruchamia domyślnie pierwsze zadanie w węźle nadrzędnym. Plik ten z pewnych powodów musi zawierać co najmniej pięć wpisów. Jeżeli liczba węzłów jest mniejsza niż pięć, to można powtórzyć kilka wpisów, uzyskując co najmniej pięć wierszy.

Jeżeli mamy klaster SMP z dwoma procesorami w węzłach, to plik machines.LINUX dla naszego ośmiowęzłowego systemu wygląda następująco:

 

n0

n1

n1

n2

n2

n3

n3

...

...

n7

n7

Zwróćmy uwagę na to, że w tym przypadku węzeł n0 występuje tylko raz, a pozostałe węzły dwukrotnie, co łącznie daje 15 wpisów dla ośmiowęzłowego klastra. Węzeł główny jest wpisany tylko raz, ponieważ MPI uruchamia domyślnie pierwsze zadanie właśnie w nim. Z takim plikiem konfiguracyjnym MPI będzie rozdzielać procesy po dwa na każdy węzeł, rozpoczynając od węzła nadrzędnego n0.

  1. Utworzyć kopie katalogu /usr/local/mpi w pozostałych węzłach n1,...,n7, jeżeli katalog /usr nie jest wspólny dla tych węzłów (co powinno mieć miejsce).

Podstawowe właściwości programów MPI

Wszystkie programy korzystające z MPI muszą zawierać wywołanie procedury MPI_Init potrzebne do inicjacji środowiska programu. Procedura ta musi być wywołana przed jakąkolwiek inną z biblioteki MPI. Ma ona dwa argumenty: wskaźnik na liczbę argumentów i wskaźnik na wektor argumentów, jak niżej:

 

int MPI_Init(int *argc, char **argv)

Każdy program korzystający z MPI musi również wywołać funkcję MPI_Finalize w celu oczyszczenia środowiska programu. Po wywołaniu MPI_Finalize nie wolno już wywoływać innych funkcji z biblioteki MPI. Wywołanie to ma postać:

 

int MPI_Finalize(void)

Po uruchomieniu programu MPI każdemu procesowi jest przydzielana unikatowa liczba całkowita zwana numerem porządkowym (ang. rank). Jeżeli jest N procesów, to ich numer porządkowy zmienia się w granicach od 0 do N-1. Procedury wysyłające i odbierające komunikaty wykorzystują ten numer do identyfikacji adresata lub nadawcy komunikatu.

Programy korzystające z MPI używają funkcji MPI_Comm_size oraz MPI_Comm_rank w celu uzyskania liczby procesów i ich numerów porządkowych. Wywołania tych funkcji mają postać:

 

int MPI_Comm_size(MPI_Comm comm, int *size)

int MPI_Comm_rank(MPI_Comm comm, int *rank)

Jako pierwszy argument w obydwu wywołaniach podawany jest tzw. komunikator MPI, który identyfikuje grupę procesów biorących udział w wymianie komunikatów. Większość z funkcji biblioteki MPI wymaga podania tego argumentu. Najczęściej używanym komunikatorem jest MPI_COMM_WORLD. Jest on zdefiniowany w bibliotece MPI i oznacza wszystkie procesy działające w ramach danego programu. Na przykład, jeżeli w programie równoległym działa N procesów, to zestaw określany przez MPI_COMM_WORLD ma rozmiar N. W większości praktycznych zastosowań grupa określana przez MPI_COMM_WORLD jest jedynym komunikatorem wymaganym do napisania równolegle działającego programu. MPI umożliwia także definiowanie dodatkowych komunikatorów określających podzbiory procesów. Dzięki temu programista może przydzielić taki podzbiór do wykonywania określonych zadań w ramach programu równoległego.

Poniżej podajemy sposób utworzenia równoległej wersji standardowego programu Hello World:

  1. Najpierw dołączamy wymagane pliki i deklarujemy zmienne:

 

#include <stdio.h>

#include "mpi.h"

 

int main(int argc, char *argv[])

{

int nproc;

int iproc

char proc_name[MPI_MAX_PROCESSOR_NAME];

int nameLength;

 

  1. Następnie inicjujemy środowisko programowe MPI:

 

MPI_Init(&argc, &argv);

  1. Pobieramy liczbę procesów i ich numery porządkowe:

 

MPI_Comm_size(MPI_COMM_WORLD, &nproc);

MPI_Comm_rank(MPI_COMM_WORLD, &iproc);

  1. Pobieramy nazwę węzła:

 

MPI_Get_processor_name(proc_name, &nameLength);

  1. Każdy proces wykonuje instrukcję printf w celu wyświetlenia informacji odebranych w etapach 3 i 4:

 

printf("Hello world, I am host %s with rank %d of %d\n",

   proc_name, iproc, nproc);

  1. Kończymy program korzystający z MPI:

 

  MPI_Finalize();

 

  return 0;

}

Kompilacja i uruchamianie prostego programu MPI

W tym podrozdziale opiszemy, jak należy kompilować i uruchamiać program korzystający z biblioteki MPI na klastrze Beowulf, biorąc jako przykład standardowy programik Hello World. Po zainstalowaniu tej biblioteki w katalogu /usr/local/mpi musimy dodatkowo zmodyfikować ścieżkę wyszukiwania plików wykonywalnych, dopisując do niej katalog /usr/local/mpi/bin. Program hello.c jest kompilowany za pomocą polecenia mpicc:

 

$ mpicc -o hello hello.c

Polecenie mpicc uruchamia kompilator języka C z odpowiednimi opcjami umożliwiającymi korzystanie z biblioteki MPI...

Zgłoś jeśli naruszono regulamin