Mi a PVM?

A PVM (Parallel Virtual Machine) egy szoftverrendszer, amellyel hálózatba kapcsolt (akár különböző típusú/architektúrájú) számítógépeket egyetlen nagy párhuzamos számítógépként lehet kezelni és vezérelni.

Viszonylag régi technológia (1989-2009), de az elosztott számítások implementálásának egy kiforrott modellje, amelyen keresztül gyorsan és könnyen megértheted és elsajátíthatod a kapcsolódó alapfogalmakat. C, C++ és Fortran nyelven is írhatsz olyan programokat, amik a PVM-re épülnek.

Szoftverrendszer?

Az általad írt C++ program elosztott futtatását egy háttérrendszer biztosítja és koordinálja, amelynek része egy függvénykönyvtár, a démon és a konzol.

Mire jó?

Amikor a PVM-ben összekapcsolod a különböző számítási egységeket, akkor létrehozol egy nagy kapacitású, elosztott memóriával rendelkező virtuális számítógépet. A mi esetünkben például az atlasz klaszter hpc2005 partíciójába eső számítógépeket összekapcsolva egy 48db 2.2GHz-es processzormaggal és 48 GB memóriával rendelkező szuperszámítógépet kapunk.

Erre a számítógépre tudsz párhuzamos programokat írni úgy, hogy a folyamatok közötti szinkronizációt és kommunikációt a PVM kivitelezi helyetted, és a program valójában több számítógépen, elosztottan fut, a kommunikáció pedig Gigabit Etherneten keresztül zajlik.

Hogyan működik?

Hogyan építem fel a virtuális gépet?

A párhuzamos virtuális gép létrehozásához az összes csomópontban el kell indítani a PVM démont és össze kell őket kapcsolni, de ezzel nem lesz sok munkád, szinte automatikusan megy az egész.

Az a klaszter, amin mi dolgozunk az ELTE-n, a következő fejgépből és 12 dolgozóból áll:

A home könyvtárad egyformán látszik minden nóduson, így bárhol is induljon el egy taszk, kezelni tudod belőle a fájljaidat.

A PVM az összes kari laborban elérhető Linux alatt, tehát a fejlesztést, funkcionális tesztelést a laboros gépeken is végezheted (illetve a saját gépedre is feltelepítheted a PVM-et). Fontos azonban, hogy az elosztottan futó szoftver éles tesztelését az ELTE atlasz klaszteren kell elvégezni, hogy megfigyelhető legyen a valódi elosztott viselkedés és a teljesítménynövekedés.

Hogyan írok PVM programot?

A PVM programod szabványos C/C++ nyelven íródik, közönséges UNIX folyamatként indul, de amikor átkerül a virtuális gépre, PVM taszk lesz belőle, és újabb taszkokat indíthat, amelyekkel kommunikálhat is. Természetesen több taszk is indítható, ettől válik párhuzamossá a program.

Az újonnan indított taszkok a PVM által koordinált sorrendben és helyen indulnak el, s ha másképp nem rendelkezünk, a PVM gondoskodik arról, hogy a virtuális számítógép minden fizikai csomópontjában arányos legyen a terhelés. Amikor a taszkok különböző számítógépeken indulnak el, elosztottá válik a program.

A taszk tehát egy folyamat (process), amely a PVM könyvtár segítéségével a PVM démont vezérelve tud újabb taszkokat indítani és azokkal kommunikálni.

A nagyszerű az egészben az, hogy a programozónak nem kell foglalkoznia azzal, melyik taszk melyik csomópontban fut, mivel a PVM egy nagy számítógépként kezeli az elosztott rendszert. Párhuzamos programot írunk, de elosztottan is futtathatjuk.

Hogyan használom?

Környezet beállítása

Ahhoz, hogy PVM programokat fordíts és futtass, a következő könyvtárszerkezetet érdemes létrehoznod a home könyvtárad alatt (pl. /users/megfel_elek):

.
|
--- pvm3
     |
     --- src
     |
     --- bin

Segítségül, a következő parancs kiadása létrehozza ezeket a könyvtárakat a megfelelő helyen:

$ cd && mkdir -p pvm3/src pvm3/bin

A forrásfájlokat a pvm3/src könyvtárba (vagy annak almappájába) kell majd elhelyezni, továbbá a forrásfájlok mellé kell másolni a Makefile.aimk állományt is, amely innen letölthető.

A pvm3/bin könyvtárba kerülnek azok a programok (futtatható állományok), amelyeket taszkként fog majd elindítani a PVM a virtuális gépen.

Fordítás

Amikor a programod fordítod, az src könyvtárba navigálsz és ott adod ki a fordítási és linkelési parancsokat.

Az aimk parancs hatására a fordítást vezérlő program elolvassa a Makefile.aimk állományt és a paramétereknek megfelelően lefordítja a PVM programod.

$ aimk

Az aimk egy olyan program, amely a make-re építve segít neked minél egyszerűbben helyesen lefordítani a PVM programod: a platformhoz és szoftverkörnyezethez igazodva készít futtatható állományt a forráskódból.

Amikor új PVM programot fejlesztesz, csak át kell írnod a meglévő Makefile-ban a fordítandó program nevét (a fájlnév a c vagy cc kiterjesztés nélkül), például:

BINS = forraskod1 forraskod2 forraskod3

Futtatás

A PVM programot a szokásos módon futtathatjuk, mint executable fájlt, vagy taszkként is elindíthatjuk a PVM-ből. Szem előtt kell tartanunk, hogy a programunk újabb taszkokat is fog indítani, amelyekhez tartozó programokat a PVM démonnak meg kell majd találnia. Alapértelmezett beállítások mellett a pvm3/bin/LINUX64 könyvtárban keresi a PVM a futtatható állományokat. Mivel mi nem ebben a könyvtárban dolgozunk, hanem az src alatt, a lefordított fájlokra szimbolikus linkeket (parancsikonokat) kell készíteni a bin könyvtárban.

$ aimk links

Az aimk links parancsot csak egyszer kell kiadnod, amikor elkezdesz dolgozni a programodon. Mindaddig amíg ugyanazokat a forráskódokat szerkeszted/fordítod, nem kell újabb linkeket létrehoznod.

A futtatás tehát történhet a szokásos shellből történő programindítással:

$ ~/pvm3/bin/LINUX64/hello

vagy PVM konzolból taszkindítással (a spawn parancs használatával):

pvm> spawn -> hello

Egy példa alkalmazás: Hello World!

Forráskódok

A párhuzamos programunk két különálló C programból áll (hello és hello_other). Futtatás során ezek kapcsolatba lépnek: a hello fogja elindítani a hello_other programot PVM-en keresztül, és üzenetet is váltanak egymással.

hello.c

#include <stdio.h>
#include "pvm3.h"

int main() {
    int tid;
    int num;
    printf("i'm t%x\n", pvm_mytid());
    pvm_spawn("hello_other", (char**)0, 0, "", 1, &tid);
    pvm_recv(-1, -1);
    pvm_upkint(&num,1,1);
    printf("from t%x: %d\n", tid, num);
    pvm_exit();
    return 0;
}

hello_other.c

#include "pvm3.h"

int main() {
    int tid = pvm_mytid();
    int ptid = pvm_parent();
    pvm_initsend(PvmDataDefault);
    pvm_pkint(&tid,1,1);
    pvm_send(ptid, 1);
    pvm_exit();
    return 0;
}

Miben különböznek ezek egy hagyományos C/C++ kódtól?

Mit csinál?

A hello program elindulás után lekérdezi a taszk-azonosítóját (ezzel regisztrálja magát a virtuális gépbe), majd elindítja a hello_other programot, mint új taszkot. Ezek után egy üzenetre vár az új taszktól, amelyet egy int típusú változóba tölt be, majd kiírja a kimenetre a másik taszk azonosítójával együtt, végül kilép a virtuális gépből és terminál.

A másik program, a hello_other indulásakor lekérdezi a maga, illetve a szülője taszk azonosítóját, elküldi a hálózaton a szülőnek (hello) az azonosítót, majd kilép a virtuális gépből és terminál.

Fordítás, futtatás

$ aimk
making in LINUX64/ for LINUX64
cc -W -Wall -I/usr/lib/pvm3/include [...] -o hello ../hello.c -L/usr/lib/pvm3/lib/LINUX64 -lpvm3 
cc -W -Wall -I/usr/lib/pvm3/include [...] -o hello_other ../hello_other.c -L/usr/lib/pvm3/lib/LINUX64 -lpvm3
$ aimk links
making in LINUX64/ for LINUX64
cd /users/daniel-h/pvm3/bin/LINUX64
ln -sf /users/daniel-h/pvm3/src/LINUX64/hello hello
ln -sf /users/daniel-h/pvm3/src/LINUX64/hello_other hello_other

PVM démon és konzol indítása:

$ pvm

A program taszkként való futtatása:

pvm> spawn -> hello
spawn -> hello
[1]
1 successful
t40002
pvm> [1:t40002] i'm t40002
[1:t40002] from t40003: 262147
[1:t40003] EOF
[1:t40002] EOF
[1] finished

pvm> 

Bővebben: konfiguráció és PVM konzol

A konzolt a pvm paranccsal indíthatod el, amely egyúttal a démont is elindítja. A konzol ezután a parancsaidra vár.

$ pvm
pvm>

A konfiguráció kezelése

A virtuális számítógéphez az add paranccsal lehet újabb számító nódusokat hozzákapcsolni.

pvm> add blade01 blade02 blade03
add blade01 blade02 blade03
3 successful
                    HOST     DTID
                 blade01    80000
                 blade02    c0000
                 blade03   100000
pvm>

A virtuális számítógép pillanatnyi felépítését (konfigurációját) a conf paranccsal lehet lekérdezni.

pvm> conf
conf
4 hosts, 1 data format
                    HOST     DTID     ARCH   SPEED       DSIG
                  atlasz    40000  LINUX64       1 0x00408c41
                 blade01    80000  LINUX64 1000000 0x00408c41
                 blade02    c0000  LINUX64 1000000 0x00408c41
                 blade03   100000  LINUX64 1000000 0x00408c41
pvm>

Nódus törlésére is van lehetőség, erre a delete parancs használható.

pvm> delete blade03
delete blade03
1 successful
                    HOST  STATUS
                 blade03  deleted

Hostfile

Az Atlasz klaszteren hostfile használata nem javasolt; a PVM lehetőséget ad arra, hogy a konzolt egy parancssori argumentummal indítsuk, amelyben felsoroljuk azokat a hosztokat, amelyeket indításkor fel szeretnénk venni a virtuális gépbe. Szeretnénk azonban felhívni a figyelmed, hogy amennyiben hosztfájlt használsz az indításkor, nem jól konfigurálódik a virtuális gép: alapértelmezetten a fejgép ugyanolyan súlyt kap, mint a blade-ek. Tegyük fel, hogy a hosts tartalma a 12 blade neve egymás után felsorolva.

$ pvm hosts
pvm> conf
conf
13 hosts, 1 data format
                    HOST     DTID     ARCH   SPEED       DSIG
                  atlasz    40000  LINUX64    1000 0x00408c41
                 blade01    80000  LINUX64    1000 0x00408c41
                 blade02    c0000  LINUX64    1000 0x00408c41
                 blade03   100000  LINUX64    1000 0x00408c41
                 ...
                 blade12   340000  LINUX64    1000 0x00408c41

Nem javasoljuk hostfile használatát, mert amint látható, a fejgéphez és a számító nódusokhoz tartozó sebesség (SPEED) attribútum megegyezik (1000), így a PVM a fejgépen is megpróbálna taszkokat indítani, hogy a taszkok eloszlása a fizikai gépeken egyenletes legyen. Ám hallgatóként maximum 20 folyamatot indíthatsz el a fejgépen, s a legtöbb esetben abnormálisan terminál a programod a processz limit elérése miatt, ellehetetlenítve a megfelelően alapos tesztelést.

Megoldás

Készíthetsz egy hosztfájlhoz hasonló fájlt, amibe PVM konzolparancsokat írsz, majd ezt a fájlt a pvm bemenetére irányítod a linux shell segítségével.

Legyen az add_hosts fájl tartalma a következő parancs:

add blade01 blade02 blade03 blade04 blade05 blade06 blade07 blade08 blade09 blade10 blade11 blade12

Majd indítsuk a PVM-et úgy, hogy a fájl tartalmát a PVM konzolra irányítjuk.

$ pvm < add_hosts
pvm> 12 successful
                    HOST     DTID
                 blade01    80000
                 blade02    c0000
                 blade03   100000
                 ...
                 blade12   340000
pvm>

A nódusok rendben hozzáadódtak a virtuális géphez, de ami ennél is fontosabb, hogy a speed értékeik is jól vannak beállítva:

pvm> conf
conf
13 hosts, 1 data format
                    HOST     DTID     ARCH   SPEED       DSIG
                  atlasz    40000  LINUX64       1 0x00408c41
                 blade01    80000  LINUX64 1000000 0x00408c41
                 blade02    c0000  LINUX64 1000000 0x00408c41
                 blade03   100000  LINUX64 1000000 0x00408c41
                 ...
                 blade12   340000  LINUX64 1000000 0x00408c41

Taszkok kezelése

Taszkok indítása

Taszkokat indítani a spawn parancs segítségével lehet.

pvm> spawn -> program

A nyíl azért kell, hogy a program kimenetét a konzolra irányítsuk.

Taszkindításkor megadhatjuk azt is, hogy hol történjen (melyik nóduson), de általában nem szoktuk (hagyjuk, hogy a PVM döntsön).

Taszkok listázása, leállítása

ps

a ps kilistázza az épp futó taszkokat

kill

a kill segítségével kilőhetsz (terminálhatsz) taszkokat az azonosítójuk alapján

reset

a reset parancs azonnal terminálja az összes futó taszkot és "újraindítja" a virtuális gépet

Kilépés a konzolból

Kétféleképp lehet a konzolból kilépni:

quit

csak a konzolt terminálja, a démonok továbbra is futnak (fejlesztés közben ezt érdemes használni

halt

a konzolt és a démonokat is terminálja, leállítja a virtuális gépet

Munkád befejeztével kijelentkezés előtt minden esetben lépj vissza a PVM konzolba és termináld a virtuális gépet a halt parancs segítségével!

Bővebben: a PVM könyvtár

Fontosabb PVM függvények

A zárthelyin is használható dokumentum a pvmhelp.txt, amelyben megtalálod a legfontosabb függvényeket, paraméterezésükkel együtt.

Külső forrás: összefoglaló - Párhuzamos algoritmusok

Taszkok kezelése

int pvm_mytid()

a futtató taszk azonosítójának lekérdezése

int pvm_parent()

a szülő taszk azonosítójának lekérdezése

int pvm_siblings(int **)

a szülő taszk által indított összes taszk azonosítói

int pvm_spawn (char *, char **, int, char *, int, int *)

új taszk indítása

int pvm_kill(int)

taszk kilövése

int pvm_exit()

folyamat kivonása a PVM virtuális gépéből

Üzenetküldés

int pvm_initsend(int)

üzenetpuffer kiürítése, encoding beállítása, felkészülés új üzenet összeállítására

int pvm_pk*(...)
Adott típusú adat hozzáadása az üzenethez

Részletesebben:

int pvm_pkbyte  (char *          , int, int)
int pvm_pkcplx  (float *         , int, int)
int pvm_pkdcplx (double *        , int, int)
int pvm_pkdouble(double *        , int, int)
int pvm_pkfloat (float *         , int, int)
int pvm_pkint   (int *           , int, int)
int pvm_pklong  (long *          , int, int)
int pvm_pkshort (short *         , int, int)
int pvm_pkstr   (char *                    )
int pvm_pkuint  (unsigned int *  , int, int)
int pvm_pkulong (unsigned long * , int, int)
int pvm_pkushort(unsigned short *, int, int)
int pvm_send(int, int)
üzenet küldése

Példa

pvm_initsend(PvmDataDefault);
pvm_pkint(array, 10, 1);
pvm_send(tid, 0);

Tipikus hiba az adatok szétdarabolása. Az összetartozó adatokat (pl. vektor, mátrix elemei) egy üzenetbe csomagold be ismételt pvm_pk* hívásokkal, és a teljes adatszerkezetet egyben küldd el!

Üzenetfogadás

int pvm_recv(int, int)
üzenet fogadása, blokkoló változat (a programvégrehajtás áll, amíg nem kapunk üzenetet)

(létezik belőle nem blokkoló változat is: pvm_nrecv)

int pvm_upk*(...)

adott típusú adat kicsomagolása az üzenetből

int pvm_upkbyte  (char *          , int, int)
int pvm_upkcplx  (float *         , int, int)
int pvm_upkdcplx (double *        , int, int)
int pvm_upkdouble(double *        , int, int)
int pvm_upkfloat (float *         , int, int)
int pvm_upkint   (int *           , int, int)
int pvm_upklong  (long *          , int, int)
int pvm_upkshort (short *         , int, int)
int pvm_upkstr   (char *                    )
int pvm_upkuint  (unsigned int *  , int, int)
int pvm_upkulong (unsigned long * , int, int)
int pvm_upkushort(unsigned short *, int, int)

Példa

pvm_recv(tid, msgtag);
pvm_upkint(tid_array, 10, 1);
pvm_upkint(problem_size, 1, 1);
pvm_upkfloat(input_array, 100, 1);

További hasznos linkek:

Gyakorlatvezetők weboldalai: