Hlavní navigace

Povídky paralelistické: programování pro paralelní počítače

Michal Burda

Denní chléb programátora paralelního superpočítače. Pokud si už teď dovedete alespoň zhruba představit, jak taková paralelní stanice vypadá, dosavadní díly našeho seriálu splnily svůj účel. Jak se ale pro paralelní počítače programuje?

Vývoj paralelního programu můžeme probíhat teoreticky několika způsoby. Můžeme použít nějaký paralelismu-schopný programovací jazyk jako Ada nebo High Performance Fortran, použít speciální paralelizační kompilátor nějakého univerzálního programovacího jazyka (jako C), který automaticky detekuje možnou souběžnost kódu v sekvenčním algoritmu, nebo psát program v obyčejném univerzálním jazyku s obyčejným překladačem a použít speciální paralelizační knihovnu jako PVM (Parallel Virtual Machine) či MPI (Message Passing Interface).

První možnost není moc oblíbená, použití paralelizačního kompilátoru je jednodušší zejména u symetrických multiprocesorů, a tak se nejpoužívanějším způsobem stala volba třetí – použití paralelizačních knihoven. O jedné z nich bude pojednávat zbytek tohoto článku.

PVM

Parallel Virtual Machine (PVM) je systém, který umožňuje programátorům pohlížet na heterogenní soubor unixových strojů jako na jednolitý paralelní počítač. PVM pracuje na jednoduchém, ale funkčně kompletním modelu předávání zpráv (message passing model).

PVM vzniklo z hladu po systému zastřešujícím heterogenní síťové prostředí. Je to sada softwarových nástrojů a programátorských knihoven, která umožňuje a podporuje provoz paralelní aplikace na obecně nestejných počítačích propojených do sítě.

Základem PVM je démon pvmd3, který běží na každém počítači. Všechny uzly se spuštěným démonem pvmd3 mohou dohromady vytvářet virtuální stroj. Zařazení uzlů do virtuálního stroje se dá různě konfigurovat, ale to pro nás není důležité. Pro hrubý přehled nám stačí vědět, že démonpvmd3 má na starosti poskytování služeb procesům, běžícím na konkrétním uzlu.

Druhou částí PVM je knihovna, poskytující rozhraní pro paralelní operace. V současné době jsou podporovány jazyky C, C++ a Fortran.

Hlavní vlastnosti PVM:

  • Uživatelsky nastavitelné pole uzlů – uživatel si může sám určit, které uzly sítě mají být na řešení jeho úkolu zainteresovány. Dělá se to tak, že se virtuálnímu stroji explicitně zadá, které počítače v síti má využívat. Změna může být dokonce prováděna i za chodu aplikace, což je důležitá vlastnost pro zajištění odolnosti vůči chybám.
  • Průhledný přístup k hardwaru – aplikace může využívat jednotlivé procesory jako bezejmenné pracující dělníky (stačí jednoduše říct: spusť danou úlohu na nějakém procesoru virtuálního stroje), nebo smí (pokud chce) určité úlohy spouštět na pevně zvolených uzlech.
  • Výpočty na bázi procesů a explicitní model předávání zpráv – výpočetní úloha se skládá z několika procesů, které spolu komunikují metodou předávání zpráv.
  • Podpora heterogenního prostředí – počítače s jedním procesorem stejně tak jako multiprocesory mohou být propojeny do jednoho paralelního virtuálního stroje. Samostatní prodejci hardwaru často dodávají své vlastní optimalizované části PVM, aby se co nejvíce využilo vlastností jejich procesorů.

A jak taková konkrétní paralelní aplikace vlastně vypadá? Ukážeme si to na jednoduchém příkladu – něco jako „Hello World!“ pro paralelní architekturu, který se skládá ze dvou zdrojových souborů.

Soubor: master.c:

#include "pvm3.h"
#define NSLAVES 5

int main() {
  int nhost; /* Počet spuštěných procesů */
  int tids[NSLAVES]; /* Pole ID spuštěných procesů */
  char buffer[128]; /* Pomocný řetězcový buffer */
  int x; /* Pomocná proměnná */

   /* Spuštění paralelního procesu jménem "slave". Jednoznačné
   * identifikátory procesů v rámci PVM (TID)
   * budou uloženy do pole tids. */
  nhost = pvm_spawn("slave", (char**) 0, PvmTaskDefault, NULL, NSLAVES, tids);

  /* Návratovou hodnotou pvm_spawn() je počet spuštěných procesů
  * nebo -1 jako chyba. */
  if (nhost > 0) {
   /* Procesy byly v pořádku spuštěny...*/

   for (x = 0; x < nhost; x++) {
     /* Čekejme na zprávu s identifikátorem 111. */
     pvm_recv(tids[x], 111);

     /* Zpráva přisla a my víme, že v ní je řetězec.
     * Zkopírujme řetězec z vnitřních bufferů PVM
     * do připraveného bufferu. */
     pvm_upkstr(buffer);

     /* Tisk na obrazovku. */
     printf("Zpráva od otroka: %s\n", buffer);
   }

  } else {
   /* Chyba ve spuštění paralelního procesu... */
  puts("Nelze spustit otroky!");
  }

  /* Ukončení */
  pvm_exit();
}

Soubor: slave.c:

#include "pvm3.h"

int main() {
  /* TID rodičovského procesu */
  int parent_tid;

  /* Získáme tid rodičovského procesu
  * (tedy toho, který nás spustil) */
  parent_tid = pvm_parent();

  /* Inicializace odesílání zpráv - vnitřní odesílací
  * buffery PVM jsou vynulovány a připraveny k uložení zprávy. */
  pvm_initsend(PvmDataDefault);

  /* "Spakování" řetězce pro odeslání */
  pvm_pkstr("Ahoj, já jsem 'slave'!");

  /* Samotné odeslání zprávy procesu s TID parent_tid
  * (rodičovský proces). Zpráva bude mít identifikátor 111. */
  pvm_send(parent_tid, 111);

  /* Konec... */
  pvm_exit();
}

Oba soubory se kompilují naprosto obvyklým způsobem a výsledkem každého z nich je samostatný spustitelný soubor (master a slave). Je důležité, aby tyto soubory (zejména program slave) byly k dispozici na každém uzlu virtuálního paralelního stroje v adresáři daném konfigurací PVM.

Soubor master.c je hlavním souborem, kterým se paralelní aplikace spouští. Jeho úkolem je pomocí funkce pvm_spawn() spustit 5 (NSLAVES) paralelních procesů slave.

Každý proces spouštěný pomocí PVM získává číslo zvané TID, které jej jednoznačně identifikuje v rámci celého virtuálního stroje. (Něco jako Process ID (PID), ale platné napříč všemi uzly virtuální mašiny.) Tato čísla vrací funkce pvm_spawn() v poli tids a jako návratovou hodnotu používá počet procesů, které se podařilo spustit.

Úkolem našeho mastera je v cyklu čekat na zprávy, které mu podřízené (slave) procesy pošlou, a jejich obsah vytisknout. Master čeká na zprávu pomocí funkce pvm_recv(), která program blokuje, dokud nějaká zpráva nepřijde. (Na okraj se sluší poznamenat, že v PVM samozřejmě existuje tzv. neblokující příjem zpráv, který funguje tak, že pomocí speciální funkce se proces jen „podívá do schránky“, jestli něco nepřišlo, a dále normálně pokračuje ve zpracování programu.)

Abychom si mohli přijatou zprávu přečíst, je třeba ji rozbalit. V ukázce se to dělá pomocí funkce pvm_upkstr(), která předpokládá, že ve zprávě je uložen řetězec.

Po přijetí zprávy od každého podřízeného procesu program master končí.

Náplní činnosti podřízených procesů je zjištění TIDu rodičovského procesu (tedy TID mastera – pvm_parent()) a odeslání krátké textové zprávy (pvm_initsend() – inicializace odesílání zpráv, pvm_pkstr() – sbalení řetězce do odesílané zprávy a pvm_send() samotné odeslání).

Z uvedené ukázky je zřejmé, jak asi opravdový paralelní program bude vypadat. Celá aplikace bude (ale nemusí tomu tak nutně být) rozdělena na několik částí, které poběží na různých počítačích v síti. Jednotlivé procesy se budou dorozumívat posíláním zpráv. Přitom zprávy lze adresovat podle TIDu konkrétnímu procesu, skupinám procesů nebo všem (oběžník, broadcast) a mohou obsahovat v podstatě cokoliv – textové řetězce (jak jsme viděli), ale i celá nebo float čísla a dokonce i složité datové struktury.

O PVM by se toho dalo samozřejmě napsat o mnoho více (třeba samostatný seriál), ale to tady není účelem. Motivací tohoto článku bylo pouze názorně ukázat, jak takové paralelní programy mohou vypadat, a pokud se mi to povedlo, budu spokojen :-).

Našli jste v článku chybu?