Továbbra is az állatorvosi Mandelbrot halmazt számoljuk, de most nem a Qt-s ösvényen, hanem az előző poszt parancssorában járunk. Az ott megismert mandelpng.c++ programmal, aztán a P-szálakkal számoló pmandelpng.c++, végül az OpenMP-t használó ompmandelpng.c++ forrásokkal dolgozunk. Fordítsuk, futtassuk az elsőt, s közben figyeljük folyamatosan a
top -H -u norbi
parancs kimenetét, hogy lássunk is valamit, a forrásban felnyomtuk az iterációs határt: iteraciosHatar = 32000;
látszik, miközben dolgozik a program:
Olyan fél perc kellett tehát a manőverhez. A PP alapján készítsünk egy olyan kódot, ami párhuzamosan tud dolgozni! A feladat erre alkalmas, hiszen az egyik szál számolja monjuk a kép első 300 sorát, egy másik szál pedig a 300-600-ig sorokat. Persze a programot általánosabban írjuk meg: egy szál a kép magassága / SZALAK_SZAMA darab sort számol majd ki. Ezt a megoldást is teszteljük!
Rend van a világban
Valóban: láthatóan fut a két szálunk:
és a végrehajtási idő is a felére csökkent:
Azért a jól felkészült mérnök informatikus hallgató észrevesz némi zavart az erőben:
- miért van külön PID-je a két P-szálnak, hova így az elmélet: a processzen belüli párhuzamosság, hogy azért választottuk ezt, hogy lássák a szálak a globális képet (amivel ugye nincs is baj, hiszen a kép rendben elkészül)
- a másik a PP 64, PP 66-os mérések kb. ugyanazt mérik mindkét esetben, de a valós idő dupla annyi...
ezekre varrjunk gombot egy-egy trófeáért persze!
OpenMP
Nem cifrázzuk: a legegyszerűbb megoldás is alkalmas, hiszen a feladatot triviálisan tudtuk párhuzamos részekre bontani. A részeket összefogó ciklus előtt jelezzük, hogy párhuzamosan akarjuk nyomni őket :
Ez a megoldás az összes lehetséges szálon párhuzamosítani fog, ez a mi esetünkben 4 magot és max. 4 szálat jelent, amit innen tudunk:
std::cout << omp_get_num_procs () << std::endl;
(A fordításnál a -O3 optimalizációs szintet se felejtsük el bekapcsolni, lásd majd a következő utáni képen.)
std::cout << omp_get_max_threads () << std::endl;
Lássuk a medvé(ket)
Szépen látszik, hogy dolgozik a 4 párhuzamos szál, adósok vagyunk még a fordítási, futtatási képpel, íme
Sokkal jobb, kb. 8 szekundumra faragtuk! Persze a -O3 kellett, s az első kettőt e nélkül toltuk. Azért tettünk így, hogy a kétszeres szorzó szépen látszódjon az első két esetben. De megmarad a rend a világban, ha mindhármat az O3 szintű optimalizációval fordítjuk. Ezzel az alábbi eredményeket toltuk:
g++ mandelpngt.c++ `libpng-config --ldflags` -O3 -o mandelpngt
time ./mandelpngt
real 0m18.691s
g++ pmandelpngt.c++ `libpng-config --ldflags` -lpthread -O3 -o pmandelpngt
time ./pmandelpngt b.png1. szal szamitas indul0. szal szamitas
real 0m9.495s
g++ ompmandelpngt.c++ `libpng-config --ldflags` -fopenmp -O3 -o ompmandelpngt
time ./ompmandelpngt c.png4
real 0m8.553s
Kattints a tovább linkre majd a forrásokhoz:
mandelpngt.c++
// Mandelbrot png // Programozó Páternoszter // // Bátfai Norbert, nbatfai@inf.unideb.hu, nbatfai@gmail.com // http://progpater.blog.hu/2011/03/26/kepes_egypercesek // // Fordítás: // g++ mandelpng.c++ `libpng-config --ldflags` -o mandelpng #include <iostream> #include "png++/png.hpp" #include <sys/times.h> int main (int argc, char *argv[]) { // Mérünk időt (PP 64) clock_t delta = clock(); // Mérünk időt (PP 66) struct tms tmsbuf1, tmsbuf2; times(&tmsbuf1); if (argc != 2) { std::cout << "Hasznalat: ./mandelpng fajlnev"; return -1; } // számítás adatai double a = -2.0, b = .7, c = -1.35, d = 1.35; int szelesseg = 600, magassag = 600, iteraciosHatar = 32000; // png-t készítünk a png++ csomaggal png::image <png::rgb_pixel> kep (szelesseg, magassag); // a számítás double dx = (b-a)/szelesseg; double dy = (d-c)/magassag; double reC, imC, reZ, imZ, ujreZ, ujimZ; // Hány iterációt csináltunk? int iteracio = 0; std::cout << "Szamitas"; // Végigzongorázzuk a szélesség x magasság rácsot: for (int j=0; j<magassag; ++j) { //sor = j; for (int k=0; k<szelesseg; ++k) { // c = (reC, imC) a rács csomópontjainak // megfelelő komplex szám reC = a+k*dx; imC = d-j*dy; // z_0 = 0 = (reZ, imZ) reZ = 0; imZ = 0; iteracio = 0; // z_{n+1} = z_n * z_n + c iterációk // számítása, amíg |z_n| < 2 vagy még // nem értük el a 255 iterációt, ha // viszont elértük, akkor úgy vesszük, // hogy a kiinduláci c komplex számra // az iteráció konvergens, azaz a c a // Mandelbrot halmaz eleme while (reZ*reZ + imZ*imZ < 4 && iteracio < iteraciosHatar) { // z_{n+1} = z_n * z_n + c ujreZ = reZ*reZ - imZ*imZ + reC; ujimZ = 2*reZ*imZ + imC; reZ = ujreZ; imZ = ujimZ; ++iteracio; } kep.set_pixel(k, j, png::rgb_pixel(255-(255*iteracio)/iteraciosHatar, 255-(255*iteracio)/iteraciosHatar, 255-(255*iteracio)/iteraciosHatar)); } std::cout << "." << std::flush; } kep.write (argv[1]); std::cout << argv[1] << " mentve" << std::endl; times(&tmsbuf2); std::cout << tmsbuf2.tms_utime-tmsbuf1.tms_utime + tmsbuf2.tms_stime-tmsbuf1.tms_stime << std::endl; delta = clock() - delta; std::cout << (double)delta/CLOCKS_PER_SEC << " sec" << std::endl; }
pmandelpngt.c++
// Mandelbrot png párhuzamosan P-szálakkal // Programozó Páternoszter // // Bátfai Norbert, nbatfai@inf.unideb.hu, nbatfai@gmail.com // http://progpater.blog.hu/2011/03/26/kepes_egypercesek // // Fordítás: // g++ pmandelpng.c++ `libpng-config --ldflags` -lpthread -o pmandelpng #include <iostream> #include "png++/png.hpp" #include <pthread.h> #include <time.h> #include <sys/times.h> #define SZALAK_SZAMA 4 // számítás adatai double a = -2.0, b = .7, c = -1.35, d = 1.35; int szelesseg = 600, magassag = 600, iteraciosHatar = 32000; // png-t készítünk a png++ csomaggal png::image <png::rgb_pixel> kep (szelesseg, magassag); void *mandel_resz_szamitas(void *id) { int mettol = *(int *)id * ( magassag / SZALAK_SZAMA); int meddig = mettol + (magassag/SZALAK_SZAMA); // a számítás double dx = (b-a)/szelesseg; double dy = (d-c)/magassag; double reC, imC, reZ, imZ, ujreZ, ujimZ; // Hány iterációt csináltunk? int iteracio = 0; std::cout << *(int *)id << ". szal szamitas indul"; // Végigzongorázzuk a szélesség x magasság rácsot: for (int j=mettol; j<meddig; ++j) { //sor = j; for (int k=0; k<szelesseg; ++k) { // c = (reC, imC) a rács csomópontjainak // megfelelő komplex szám reC = a+k*dx; imC = d-j*dy; // z_0 = 0 = (reZ, imZ) reZ = 0; imZ = 0; iteracio = 0; // z_{n+1} = z_n * z_n + c iterációk // számítása, amíg |z_n| < 2 vagy még // nem értük el a 255 iterációt, ha // viszont elértük, akkor úgy vesszük, // hogy a kiinduláci c komplex számra // az iteráció konvergens, azaz a c a // Mandelbrot halmaz eleme while (reZ*reZ + imZ*imZ < 4 && iteracio < iteraciosHatar) { // z_{n+1} = z_n * z_n + c ujreZ = reZ*reZ - imZ*imZ + reC; ujimZ = 2*reZ*imZ + imC; reZ = ujreZ; imZ = ujimZ; ++iteracio; } kep.set_pixel(k, j, png::rgb_pixel(255-(255*iteracio)/iteraciosHatar, 255-(255*iteracio)/iteraciosHatar, 255-(255*iteracio)/iteraciosHatar)); } std::cout << "." << std::flush; } return id; } int main (int argc, char *argv[]) { // Mérünk időt (PP 64) clock_t delta = clock(); // Mérünk időt (PP 66) struct tms tmsbuf1, tmsbuf2; times(&tmsbuf1); if (argc != 2) { std::cout << "Hasznalat: ./mandelpng fajlnev"; return -1; } pthread_t szal[SZALAK_SZAMA]; int szal_arg[SZALAK_SZAMA]; int i, *r; // SZALAK_SZAMA db P-szálat készítünk for (i=0; i<SZALAK_SZAMA; ++i) { szal_arg[i] = i; // a párhuzamosan végrehajtandó kód a // mandel_resz_szamitas függvényben if (pthread_create(szal+i, NULL, mandel_resz_szamitas, (void *)(szal_arg+i))) exit(-1); } // Megvárjuk, hogy minden szál befejezze a melót for (i=0; i<SZALAK_SZAMA; ++i) { pthread_join(szal[i], (void **) &r); std::cout << *r << ". szal kesz."; } // Ha kész vannak a szálak, kinyomjuk fájlba a képet kep.write (argv[1]); std::cout << argv[1] << " mentve" << std::endl; times(&tmsbuf2); std::cout << tmsbuf2.tms_utime-tmsbuf1.tms_utime + tmsbuf2.tms_stime-tmsbuf1.tms_stime << std::endl; delta = clock() - delta; std::cout << (double)delta/CLOCKS_PER_SEC << " sec" << std::endl; }
ompmandelpngt.c++
// Mandelbrot png // Programozó Páternoszter // // Bátfai Norbert, nbatfai@inf.unideb.hu, nbatfai@gmail.com // http://progpater.blog.hu/2011/03/26/kepes_egypercesek // // Fordítás: // g++ ompmandelpngt.c++ `libpng-config --ldflags` -fopenmp -O3 -o ompmandelpngt #include <iostream> #include "png++/png.hpp" #include <sys/times.h> #include <omp.h> int main (int argc, char *argv[]) { // Mérünk időt (PP 64) clock_t delta = clock(); // Mérünk időt (PP 66) struct tms tmsbuf1, tmsbuf2; times(&tmsbuf1); std::cout << omp_get_num_procs () << std::endl; std::cout << omp_get_max_threads () << std::endl; if (argc != 2) { std::cout << "Hasznalat: ./mandelpng fajlnev"; return -1; } // számítás adatai double a = -2.0, b = .7, c = -1.35, d = 1.35; int szelesseg = 600, magassag = 600, iteraciosHatar = 32000; // png-t készítünk a png++ csomaggal png::image <png::rgb_pixel> kep (szelesseg, magassag); // a számítás double dx = (b-a)/szelesseg; double dy = (d-c)/magassag; double reC, imC, reZ, imZ, ujreZ, ujimZ; // Hány iterációt csináltunk? int iteracio = 0; std::cout << "Szamitas"; // Végigzongorázzuk a szélesség x magasság rácsot: #pragma omp parallel for for (int j=0; j<magassag; ++j) { //sor = j; for (int k=0; k<szelesseg; ++k) { // c = (reC, imC) a rács csomópontjainak // megfelelő komplex szám reC = a+k*dx; imC = d-j*dy; // z_0 = 0 = (reZ, imZ) reZ = 0; imZ = 0; iteracio = 0; // z_{n+1} = z_n * z_n + c iterációk // számítása, amíg |z_n| < 2 vagy még // nem értük el a 255 iterációt, ha // viszont elértük, akkor úgy vesszük, // hogy a kiinduláci c komplex számra // az iteráció konvergens, azaz a c a // Mandelbrot halmaz eleme while (reZ*reZ + imZ*imZ < 4 && iteracio < iteraciosHatar) { // z_{n+1} = z_n * z_n + c ujreZ = reZ*reZ - imZ*imZ + reC; ujimZ = 2*reZ*imZ + imC; reZ = ujreZ; imZ = ujimZ; ++iteracio; } kep.set_pixel(k, j, png::rgb_pixel(255-(255*iteracio)/iteraciosHatar, 255-(255*iteracio)/iteraciosHatar, 255-(255*iteracio)/iteraciosHatar)); } std::cout << "." << std::flush; } kep.write (argv[1]); std::cout << argv[1] << " mentve" << std::endl; times(&tmsbuf2); std::cout << tmsbuf2.tms_utime-tmsbuf1.tms_utime + tmsbuf2.tms_stime-tmsbuf1.tms_stime << std::endl; delta = clock() - delta; std::cout << (double)delta/CLOCKS_PER_SEC << " sec" << std::endl; }