Bevezetés a párhuzamos feldolgozásba


Előző oldalKövetkező oldalHome


Ez a bevezető Hank Dietz: Linux Parallel Processing HOWTO c. írásának felhasználásával készült.

Mi a párhuzamos feldolgozás?

A párhuzamos feldolgozás azon az elgondoláson alapul, hogy a programfutás felgyorsítása érdekében a kódot több részre bontjuk, s ezeket egyidejűleg, külön-külön processzoron futtatjuk. Az n processzorra szétosztott program legkedvezőbb esetben n-szer gyorsabban fut annál, mint ahogy egy processzoron futna.

Régebben a többprocesszoros párhuzamos futtatás elsősorban speciálisan tervezett (és emiatt rendkívül költséges) multiprocesszoros gépekben volt elérhető. Más operációs rendszerekhez hasonlóan a Linux is támogatja a szimmetrikus multiprocesszoros (SMP) rendszereket (amelyeket általában "szerver" gépként árulnak). Ezekben az SMP rendszerekben több egyenrangú processzor osztozik a memórián és a perifériás készülékekhez illeszkedő síndrendszeren.

Egy másik lehetőség az, hogy számítógépek egy csoportját (például Linux rendszert futtató PC-ket) hálózatra fűzve alakítsunk ki párhuzamos feldolgozásra alkalmas fürtöt.

A harmadik lehetőség az, hogy a processzorok multimédiás utasításkészletét (pl. MMX) kihasználva írjunk olyan programot, amely egyidejűleg több egész számon tudja végrehajtani ugyanazt az utasítást.

Végül az is lehetséges, hogy a Linux rendszer (akár egy közönséges PC) "host rendszer" legyen egy párhuzamos feldolgozásra tervezett célrendszer (pl. transputer kártyák) működtetéséhez.

Bennünket elsősorban az első két megoldás érdekel: a szimmetrikus multiprocesszálás, a kétprocesszoros munkaállomás kihasználásához, és a fürtözés ami a továbbfejlesztés lehetőségét biztosítja. Célhardvert (lásd 4. módszer) nem kívánunk vásárolni, a multimédiás utasításokkal pedig csak igen korlátozott esetekben és módon lehet érdemleges gyorsítást elérni.

Érdemes nekem a párhuzamos feldolgozással foglalkozni?

Noha a processzorok számának növelésével sok feldolgozás meggyorsítható, a legtöbb alkalmazás mégsem tudja kihasználni a párhuzamosítás előnyeit. Általában azt mondhatjuk hogy a párhuzamos feldolgozástól csak akkor várhatunk eredményt, illetve akkor érdemes foglalkozni vele, ha:

Szimmetrikus multiprocesszálás Linux alatt

A röviden csak SMP néven emlegetett szimmetrikus multiprocesszálás azt az operációs rendszer koncepciót jelenti, amelyikben egyenrangú processzorok bármelyike bármely feladatot el tudja látni. Az SMP rendszerekben tipikusan több kódrészlet fut egyszerre, melyek külön-külön adatokat dolgoznak fel párhuzamosan (Multiple instructions - multiple data: MIMD), osztott memóriájú üzemmódban.

SMP hardver

Habár SMP rendszerek már régóta léteznek, ezek a legutóbbi időkig annyira eltértek egymástól hardver felépítésükben, hogy nem lehetett rájuk hordozható operációs rendszert írni, mindegyiknek megvolt a maga sajátossága. A helyzet gyökeresen megváltozott, az Intel Multiprocesszor Specifikációja megjelenésével (MPS). Az egyetlen nem Intel architektúrájú rendszer, amelyet a Linux támogat, a Sun4m SPARC multiprocesszoros gépcsalád. Az Intel processzorai közül a Linux támogatja a 486DX, Pentium, Pentium MMX, Pentium Pro és a Pentium II (beleértve a Xeont is) családokat támogatja. Az AMD és Cyrix processzorok más rendszerű SMP mechanizmussal rendelkeznek, jelenleg nem tudunk olyan alaplapról, amely ezen processzorok SMP mechanizmusához igazodna...).

Az órajel, az egyes utasítások és a memória műveletek sebességén kívül az is befolyásolja az SMP rendszerek teljesítményét, hogy a külső gyorsítótár (L2 cache) megosztott-e, vagy az egyes processzorok külön-külön gyorsítótárral rendelkeznek. Általában a saját gyorsítótárral rendelkező processzorok esetében nagyobb teljesítmény várható, de az SMP Linux esetében nem ennyire triviális a helyzet: a jelenlegi ütemező ugyanis nem foglalkozik azzal, hogy az egyes feladatokat az egymást követő időszeletekben mindig ugyanahhoz a processzorhoz próbálja hozzárendelni (ezt processzoraffinitásnak hívják), márpedig processzorváltásnál a külön L2 cache előnye nem érvényesül (sőt, akár hátrányosabb is lehet). A közeljövőben (a 2.4-es verziójú kernel megjelenésekor) talán változik a helyzet, mivel az SMP Linux közösségben fölmerült már az igény a változtatásra, és voltak ilyen tárgyú viták "processzor binding" címszó alatt.

Bár egy többprocesszoros gép nagymértében fel tudja gyorsítani a számolásigényes feladatokat, a gépben mégis csak egyetlen (manapság többnyire PCI) I/O sínrendszer van, amelyik érthető okokból nem képes nagyobb teljesítményre, mint az egyprocesszoros rendszerben ... sőt, a processzorok közötti egyezkedés miatt a valóságban egy kicsit még rosszabb is a helyzet. Ha az I/O műveleteket is fel akarjuk gyorsítani, akkor több független PCI vagy SCSI csatornával ellátott rendszert kell kiépíteni. Érdemes szem előtt tartani, hogy a jelenlegi SMP Linux egyidejűleg csak egy processzor számára engedélyezi a rendszermag szintjén való futást (itt helyezkednek el a rendszerhívások és az eszközmeghajtó programrészek), így olyan eszközvezérlőt célszerű használni, amelyek kiszolgálása minimális CPU időt igényel (DMA, vagy SCSI vezérlők). Ha igazán nagy I/O teljesítményre van szükség, az eszközök közvetlen kezelése is megfontolandó megoldás.

Meg kell jegyezni, hogy az I/O sínrendszer sebessége és a processzor órajele időnként nehezen követhető kapcsolatban állnak egymással. Például a Pentium 133 MHz CPU esetében a PCI sínrendszer nagyobb frekvencián (33 MHz) működik, mint a Pentium 150 MHz CPU esetén (30 MHz). Érdemes tehát gondosan utánanézni, hogy melyik processzor milyen alapfrekvenciából, milyen szorzóval állítja elő a belső órajelét. SMP rendszerek esetében, ahol az I/O átviteli sebesség lehet a legszűkebb keresztmetszet, még nagyobb fontosabb lehet, hogy a sínrendszer órajelének frekvenciáját maximalizáljuk.

Az SMP rendszer programozása

OK, eldöntöttük, hogy a szimmetrikus multiprocesszálás hasznos dolog, de... hogyan is kezdjünk hozzá? A független folyamatokat valahogy koordinálni kell, ami kommunikációt igényel. Az SMP rendszerekben ez többnyire a megosztott memóriahasználat révén valósul meg. Az elv egyszerű: az egyik processzor lerak valamit a memóriába, a másik meg beolvassa - de a valóságban ez sajnos nem megy ilyen egyszerűen. A bonyodalmakat többek között az okozza, hogy a futtatható feladatok száma nem mindig egyezik meg a processzorok számával, nem tudjuk követni, hogy melyik processzor éppen melyik feladathoz fog hozzá, és mikor teszi félre ideiglenesen. A megosztott memóriában tárolt adatokhoz való hozzáférést tehát valahogyan szinkronizálni kell. Ezen kívül arra is ügyelni kell, hogy az adatokon végzett elemi műveletek ne lapolódjanak át, tehát az atomicitás feltétele teljesüljön. (Például egy szummázó változó értének megnövelése több gépi utasításból is állhat: beolvassuk a változót, hozzáadjuk a részösszeget, majd eltároljuk a memóriába. Nagyon kellemetlen volna, ha ezen három lépés közben egy másik folyamat belebabrálna a szummázó változóba...) Az osztott memóriában tárolt adatok integritásának megóvására és a folyamatok közötti kommunikáció megvalósítására többféle szoftver megoldás ismeretes.

A legegyszerűbb megoldás az, ha mindent a z operációs rendszer többfeladatos (multitasking) képességeire bízunk, s felhasználói szinten egymástól független feladatokat futtatunk, legalább annyit egyszerre, ahány processzor van. Ha több felhasználó végez egyidejűleg időigényes számításokat, vagy ha egy felhasználó több esetre (többféle bemenő paraméterrel) számol ugyanazzal a programmal, akkor a rendszer automatikusan szétosztja a feladatokat a rendelkezésre álló CPU-k között, és saját beépített mechanizmusaival gondoskodik az adatok épségének megóvásáról, valamint az erőforrások optimális felhasználásáról. Ekkor ugyan az egyes feladatok semmivel sem futnak le hamarabb, de a feladatok összességét tekintve már tetemes időnyereség érhető el ahhoz az esethez képest, amikor egy processzorért kell versengeniük a programoknak (időszeleteléses rendszerben). E módszer legfőbb előnye, hogy semmiféle módosítást nem kell végrehajtani a programokon.

Bonyolultabb esetben, amikor azt akarjuk, hogy egy bizonyos feladat fusson több processzoron, akkor módosítani kell a programot, "párhuzamosítani" kell. Két, alapvetően különböző megközelítés létezik a megosztott memóriájú programozásban: megosztani mindent, vagy megosztani valamit. Az előbbi esetben ugyanaz az utasítássorozat fut egyszerre, több példányban, több adaton, párhuzamosan. A második esetben nekünk kell előírni, hogy mely adatokat osztjuk meg, s ez esetben különböző kódrészletek futhatnak párhuzamosan.

A "share everything" koncepció egyik elterjedt megvalósítása a többszálú futtatást lehetővé tevő LinuxThreads könyvtár. Ebben a megoldásban a program a clone() függvényhívással többszörözheti meg magát, hogy párhuzamosan több példányban fusson.

A "share something" koncepció elve hogy "csak azt osszuk meg, amit muszáj közösen használni". Jelenleg két, egymáshoz hasonló mechanizmus áll rendelkezésre ezen koncepció megvalósításához: egyik a System V IPC/Shared Memory mechanizmus, a másik pedig a Memory mapping, ami a mmap() rendszerhíváson keresztül érhető el. A megosztott memóriában tárolt adatok kezelése néhány további problémát is felvet.

Bár többé-kevésbé triviális, mégis megemlítjük, hogy az operációs rendszer adminisztrációs veszteségeinek minimalizálás akkor valósítható meg, ha az egyidejűleg futó folyamatok száma megegyezik a processzorok számával. Ha a hosszan futó programokkal nem akarjuk a rendszer válaszidejét rontani, azaz nagyobb prioritást kívánunk adni a kevésbé CPU igényes interaktív taskoknak, akkor a számolásigényes feladatokat nice paranccsal kisebb prioritással indítsuk.

Külön problémát vet fel az az eset, amikor több felhasználó futtat egyidejűleg több processzoron futó programot. Ennek a konfliktushelyzetnek a kezelésére a "gang prioritás" a szabványmegoldás, azaz a prioritások olyan beállítása, hogy egyidejűleg mindig egy párhuzamosított program összetartozó feladatai fussanak. Meg kell jegyezni, hogy minden fölösleges taszkváltás a rendszer adminisztrációs feladatait növeli, azaz egy négyprocesszoros gépen két darab, egyenként két processzorra párhuzamosított program hatékonyabban fut, mint egyenként 4-4 processzorra párhuzamosított változata, a "gang prioritással" versengve.

Példa algoritmus

A különféle párhuzamos programozási megközelítések jobb megértéséhez egy egyszerű mintapéldát mutatunk be. Az algoritmus Pi közelítő értékét számolja ki - egy igen rossz hatékonyságú módszerrel, az 1/(1+x^2) függvénygörbe alatti terület kiszámításával. Az egyszerű soros program C nyelven így néz ki:

Az egyszerű soros program listája

  #include <stdlib.h>
  #include <stdio.h>
  main(int argc, char **argv)
  {
    register double width, sum;
    register int intervals, i;

    /* get the number of intervals */
    intervals = atoi(argv[1]);
    width = 1.0 / intervals;

    /* do the computation */
    sum = 0;
    for (i=0; i<intervals; ++i) {
      register double x = (i + 0.5) * width;
      sum += 4.0 / (1.0 + x * x);
    }
    sum *= width;
    printf("Estimation of pi is %f\n", sum);
    return(0);
  }

A többszálú futtatást támogató programkönyvtárak között a LinuxThreads egy eléggé teljes és megbízható implementációja a POSIX 1003.1c threads szabványnak. A programszálak indítását a clone() függvényhívással végzi. A POSIX kompatibilitás azt jelenti, hogy viszonylag könnyen adaptálható vele az a sok többszálú futást használó alkalmazás, amely a POSIX kompatibilis operációs rendszerekben már rendelkezésre áll.

A többszálú futtatás röviden a következő lépésekből áll:

  1. Induljon a program egyszerű folyamatként.
  2. A reteszeket pthread_mutex_t típusú változóként kell deklarálni. Ezután használjuk a pthread_mutex_init(&lock,val) függvényhívást minden használni kívánt retesz inicializálására.
  3. Új programszál indításához először is pthread_t típusú változóként deklarálni kell minden programszálat, majd a pthread_t thread nevű, az f() függvényt futtató programszál létrehozásához adjuk ki a pthread_create(&thread,NULL,f,&arg) függvényhívást. (tehát az azonosító mellett meg kell adni, hogy milyen függvényt, milyen argumentumlistával indítson)
  4. A programszálakon belül ügyeljünk a reteszek használatára, ahol ez szükséges.
  5. Használjuk a pthread_join(thread,&retval) függvényhívást a programszálak utáni "kitakarításra".
  6. A program fordításakor használjuk a -D_REENTRANT opciót
Példaprogram: Pi kiszámítása a LinuxThreads használatával, két programszál párhuzamos futtatásával
  #include <stdio.h>
  #include <stdlib.h>
  #include "pthread.h"

  volatile double pi = 0.0;  /* Approximation to pi (shared) */
  pthread_mutex_t pi_lock;   /* Lock for above */
  volatile double intervals; /* How many intervals? */

  void *
  process(void *arg)
  {
    register double width, localsum;
    register int i;
    register int iproc = (*((char *) arg) - '0');

    /* Set width */
    width = 1.0 / intervals;

    /* Do the local computations */
    localsum = 0;
    for (i=iproc; i<intervals; i+=2) {
      register double x = (i + 0.5) * width;
      localsum += 4.0 / (1.0 + x * x);
    }
    localsum *= width;

    /* Lock pi for update, update it, and unlock */
    pthread_mutex_lock(&pi_lock);
    pi += localsum;
    pthread_mutex_unlock(&pi_lock);

    return(NULL);
  }

  int
  main(int argc, char **argv)
  {
    pthread_t thread0, thread1;
    void * retval;

    /* Get the number of intervals */
    intervals = atoi(argv[1]);
    /* Initialize the lock on pi */
    pthread_mutex_init(&pi_lock, NULL);

    /* Make the two threads */
    if (pthread_create(&thread0, NULL, process, "0") ||
        pthread_create(&thread1, NULL, process, "1")) {
      fprintf(stderr, "%s: cannot make thread\n", argv[0]);
      exit(1);
    }

    /* Join (collapse) the two threads */
    if (pthread_join(thread0, &retval) ||
        pthread_join(thread1, &retval)) {
      fprintf(stderr, "%s: thread join failed\n", argv[0]);
      exit(1);
    }

    /* Print the result */
    printf("Estimation of pi is %f\n", pi);

    /* Check-out */
    exit(0);
  }

A System V IPC (Inter-Process Communication) támogatás olyan rendszerhívásokból áll, amelyek üzenetküldő, szemaforkezelő és osztott memória használatot támogató mechanizmust valósítanak meg. Bár eredetileg ezek a funkciók arra készültek, hogy egyprocesszoros de többfeladatos környezetben kommunikálhassanak egymással a folyamatok, természetesen ezek SMP Linux alatt is ugyanúgy működnek, függetlenül attól, hogy éppen melyik processzoron futnak.

Az alapvető lépések:

  1. Induljon a program szimpla folyamatként.
  2. A megosztott memóriaterület létre kell hozni az shmget() hívással, vagy az azonosítója alapján egy már korábban létrehozott megosztott memóriaszegmenshez kell hozzáférést biztosítani.
  3. Csatlakozni kell a megosztott memóriaszegmenshez (attach). A megosztott memóriaszegmens virtuális kezdőcímének mutatója a shmptr = shmat(shmid, 0, 0) utasítással kapható meg.
  4. Ahhoz, hogy majd az utolsó kicsatlakozás felszabadítsa a lefoglalt területet, ki kell adni a shmctl(shmid, IPC_RMID, 0) hívást.
  5. A szabványos Linux fork() hívással létre kell hozni a program kívánt számú másolatát. Minden folyamat megörökli a megosztott memóriaszegmenst.
  6. Amikor egy programszál befejezi a ténykedését, akkor vissza kell adni (detach) a lefoglalt memóriaterületet. Ehhez a shmdt(shmptr) hívást kell kiadni. A tényleges felszabadítás csak akkor történik meg, ha már az utolsó programszál is kijelentkezett.
Az alábbi program Pi közelítő értékét két programszálon futva számolja ki, a System V IPC hívások felhasználásával.
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <sys/ipc.h>
  #include <sys/shm.h>

  volatile struct shared { double pi; int lock; } *shared;

  inline extern int xchg(register int reg,
  volatile int * volatile obj)
  {
    /* Atomic exchange instruction */
  __asm__ __volatile__ ("xchgl %1,%0"
                        :"=r" (reg), "=m" (*obj)
                        :"r" (reg), "m" (*obj));
    return(reg);
  }

  main(int argc, char **argv)
  {
    register double width, localsum;
    register int intervals, i;
    register int shmid;
    register int iproc = 0;;

    /* Allocate System V shared memory */
    shmid = shmget(IPC_PRIVATE,
                   sizeof(struct shared),
                   (IPC_CREAT | 0600));
    shared = ((volatile struct shared *) shmat(shmid, 0, 0));
    shmctl(shmid, IPC_RMID, 0);

    /* Initialize... */
    shared->pi = 0.0;
    shared->lock = 0;

    /* Fork a child */
    if (!fork()) ++iproc;

    /* get the number of intervals */
    intervals = atoi(argv[1]);
    width = 1.0 / intervals;

    /* do the local computations */
    localsum = 0;
    for (i=iproc; i<intervals; i+=2) {
      register double x = (i + 0.5) * width;
      localsum += 4.0 / (1.0 + x * x);
    }
    localsum *= width;

    /* Atomic spin lock, add, unlock... */
    while (xchg((iproc + 1), &(shared->lock))) ;
    shared->pi += localsum;
    shared->lock = 0;

    /* Terminate child (barrier sync) */
    if (iproc == 0) {
      wait(NULL);
      printf("Estimation of pi is %f\n", shared->pi);
    }

    /* Check out */
    return(0);
  }

Fürtözés Linux rendszerekkel

A szimmetrikus multiprocesszálás föntiekben röviden vázolt ismertetése után most a másik lehetősét tárgyaljuk meg: párhuzamos feldolgozás hálózatba kötött munkaállomásokkal, vagy fürtözéssel. Itt lényegében arról van szó, hogy az egy feladaton párhuzamosan dolgozó processzorok számát úgy növeljük meg, hogy hálózatba kötött gépeken osztjuk szét a feladatot, s ezek hálózati kapcsolaton keresztül kommunikálnak egymással. Ha a processzorok egyenrangú, teljes kiépítésű, általános célra is használható munkaállomásokon futnak, akkor hálózatba kötött munkaállomásokról (networked workstations) beszélünk. Fürtözés esetén többnyire csak egy (vagy egynéhány) gép van felszerelve billentyűzettel, monitorral, a többi gép csak egy dedikált hálózaton keresztül férhető hozzá, s az összekötő hálózat elkülönül a külső, általános felhasználású hálózattól. Fürtözésnél a kommunikációs veszteség csökkentésére sokszor különlegesen kialakított hálózati topológiát (pl. Hypercube) alakítanak ki. Nem lehet általános szabályt adni, hogy milyen felépítésű és elrendezésű fürt adja az optimális teljesítményt, ez nagymértékben függ az adott alkalmazás(ok) jellegétől.

Míg SMP számítógépekből alig látunk 4 processzorosnál nagyobbat Linux alatt futni, hálózatba kötött munkaállomások vagy speciálisan kialakított fürtök esetén nem ritka a 16 vagy 32 processzoros rendszer, de 140 illetve 400 processzort tartalmazó szuperszámítógépet is építettek már. Nyilvánvaló, hogy egy sokgépes fürtben a meghibásodott egység cseréje vagy javítása kevesebb fennakadással jár, mint egy sokprocesszoros rendszernél. Fontos szempont a skálázhatóság: ha kicsinek bizonyul a rendszer, csak újabb gépeket kell csatlakoztatni, és így elvileg korlátlanul növelhető a teljesítmény.

OK, ha a fürtözés ilyen egyszerű és előnyös, akkor miért nem ezt használja mindenki? Melyek azok a hátrányok, amelyekkel szembe kell néznünk?

Néhány kivételtől eltekintve a hálózati hardver nem párhuzamos feldolgozáshoz lett tervezve, így a tipikus késleltetési idők hálózati adatátvitel esetén jóval nagyobbak, mint az SMP rendszereken belül. Pl. egy SMP gépen belül a tipikus adatátviteli sebesség gyakran meghaladja a 100 MB/s értéket, míg a Gigabit Ethernet kivételével a hálózati eszközök ennél 10-100-szor lassabb átvitelt tesznek lehetővé. Ha a hálózat átvitelei sebessége ennyire gyenge egy különálló fürt esetén, méginkább az abban az esetben, ha más célra is használt hálózaton keresztül kapcsolódnak egymáshoz a párhuzamos feldolgozást végző munkaállomások.

Meg kell említeni azt is, hogy kevés szoftver támogatás van a fürt, mint egységes rendszer kezelésére. Például a ps utasítás csak az egy gépen futó folyamatokat listázza ki, nem vesz tudomást a többi gépről.

Így a fürtözés nagy lehetőség, melynek kiaknázása nem megy minden nehézség nélkül. A kedvező hír az, hogy egyre több olyan szoftver jelenik meg, amely segít abban, hogy jó teljesítményt érjünk el a párhuzamosítással, s komoly előrehaladás látszik a hálózati eszközök fejlesztése terén is. Ma például már mindenki számára elérhető árú lett a 100 Mbit/sec sebességű hálózat, ami néhány évvel ezelőtt még elképzelhetetlen volt. A komolyabb - értsd anyagiakban nem szűkölködő - helyeken pedig már az 1 Gbit/sec sebesség a szokványos. Az átviteli sebesség mellett érdemes gondot fordítani egy nem kevésbé fontos paraméterre, a minimális késletetési időre (latency).

A hálózat adatátviteli sebesség növelésének egyik járható útja az is, hogy több vonalat fogunk össze párhuzamosan (channel bonding néven találunk erre hivatkozást a Linuxos berkekben). A jelenlegi Linux kernelek nem támogatják ezt az üzemmódot, így külön kell beszerezni az ehhez szükséges foltozást, és a módosított kernelt újra kell fordítani.

A fürtözést támogató szoftver segédletek

A fürtön vagy hálózatba kötött munkaállomásokon folyó párhuzamos feldolgozás megvalósításához kommunikációs eszközök kellenek, melyek segítségével a feladat szétosztható, illetve az adatok átadhatók, az eredmények pedig összegyűjthetők. A kapcsolattartás többnyire valamilyen üzenetközvetítő könyvtári függvények (MPI - Message Passing Interface) segítségével, a programok távindítása pedig rsh vagy ssh használatával történik.

Az üzenetközvetítő könyvtárak közül a PVM (Parallel Virtual Machine) ma mát de facto szabványnak tekinthető. Több Linux disztribúciónak is része (Red Hat Linux esetén a PowerTools gyűjteményben található meg). Az új, hivatalos MPI szabványhoz illeszkedő segédletek közül pedig az MPICH (MPI Chameleon) és a LAM a leginkább elterjedtek. A Pi kiszámítására föntebb mutatott minta algoritmus például MPICH esetében így néz ki:

  #include <stdlib.h>
  #include <stdio.h>
  #include <mpi.h>

  main(int argc, char **argv)
  {
    register double width;
    double sum, lsum;
    register int intervals, i;
    int nproc, iproc;

    if (MPI_Init(&argc, &argv) != MPI_SUCCESS) exit(1);
    MPI_Comm_size(MPI_COMM_WORLD, &nproc);
    MPI_Comm_rank(MPI_COMM_WORLD, &iproc);
    intervals = atoi(argv[1]);
    width = 1.0 / intervals;
    lsum = 0;
    for (i=iproc; i<intervals; i+=nproc) {
      register double x = (i + 0.5) * width;
      lsum += 4.0 / (1.0 + x * x);
    }
    lsum *= width;
    MPI_Reduce(&lsum, &sum, 1, MPI_DOUBLE,
               MPI_SUM, 0, MPI_COMM_WORLD);
    if (iproc == 0) {
      printf("Estimation of pi is %f\n", sum);
    }
    MPI_Finalize();
    return(0);
  }


Előző oldalKövetkező oldalHome

Gemini Projekt Elekronspektroszópiai Osztály, MTA ATOMKI, Debrecen