Úvod do OpenMP Jiří Fürst Osnova: Úvod do paralelního programování Počítače se sdílenou pamětí Základy OpenMP Sdílené a soukromé proměnné Paralelizace cyklů Příklady
Úvod do paralelního programování Počítač se sdílenou pamětí Počítač distribuovanou pamětí CPU RAM RAM CPU Řadič RAM CPU Síť CPU všechny CPU vidí celou paměť každý CPU vidí jen svou paměť Terminologie: T 1 čas pro běh na 1 CPU, T N čas pro N procesorů Zrychlení (speedup): T 1 / T N, účinnost (efficiency): T 1 /(N.T N ). 100% Amdahlův zákon: Zrychlíme-li p% kódu s-krát, pak zrychlení bude shora omezeno výrazem 100/[ (100-p) + p/s ].
Základy OpenMP Standard pro programování počítačů se sdílenou pamětí Popis a další informace na www.openmp.org Použitelný v jazycích Fortran/C/C++ Výhody proti MPI/PThreads: Jednoduchost Přenositelnost Možnost paralelizovat pouze kritické části kódu Kód lze neparalelně přeložit i překladačem nepodporujícím OpenMP Nevýhody: Nutný překladač/preprocesor s podporou OpenMP Omezeno na počítače se sdílenou pamětí Většinou nižší škálovatelnost než MPI
Základy OpenMP Struktura OpenMP: Direktivy pro paralelní překlad nejdůležitější část Knihovní funkce -není nutné využívat Systémové proměnné vhodné znát alespoň OMP_NUM_THREADS Příklad: #include <stdio.h> Program OMP1 Vlákno 0 Vlákno 1 i=10 void main() { int i, j; i = 10; #pragma omp parallel { j = 20; printf("%i\n", j); }; printf("ahoj\n"); } i = 10!$omp parallel j = 20 print *, j!$omp end parallel print *, "Ahoj" end program OMP1 j=20 tisk j Tisk Ahoj j=20 tisk j
Sdílené a soukromé proměnné #include <stdio.h> #include <omp.h> Vlákno 0 Vlákno 1 void main() { int j; #pragma omp parallel { j = omp_get_thread_num(); printf("%i\n", j); }; } Čas j=0 tisk j j=1 tisk j Co tento program vytiskne? Každé vlákno vytiskne své číslo? Obě vytisknou stejná čísla? Je-li j ve sdílené paměti, tak to pokaždé dopadne jinak!
Sdílené a soukromé proměnné #include <stdio.h> #include <omp.h> Vlákno 0 Vlákno 1 void main() { int j; #pragma omp parallel { j = omp_get_thread_num(); printf("%i\n", j); }; } Čas j=0 tisk j j=1 tisk j Co tento program vytiskne? Každé vlákno vytiskne své číslo? Obě vytisknou stejná čísla? Je-li j ve sdílené paměti, tak to pokaždé dopadne jinak! Výstup: 0 1
Sdílené a soukromé proměnné #include <stdio.h> #include <omp.h> Vlákno 0 Vlákno 1 void main() { int j; #pragma omp parallel { j = omp_get_thread_num(); printf("%i\n", j); }; } Čas j=0 j=1 tisk j tisk j Co tento program vytiskne? Každé vlákno vytiskne své číslo? Obě vytisknou stejná čísla? Je-li j ve sdílené paměti, tak to pokaždé dopadne jinak! Výstup: 1 1
Sdílené a soukromé proměnné #include <stdio.h> #include <omp.h> Vlákno 0 Vlákno 1 void main() { int j; #pragma omp parallel private(j) { j = omp_get_thread_num(); printf("%i\n", j); }; } Čas j0=0 tisk j0 j1=1 tisk j1 Chtěl bych, aby si vlákna nemohla přepsat hodnoty j! Řešením je umístit j do soukromé paměti pomocí private(j). Překladač pak vytvoří pro každé vlákno novou proměnnou j! Tak bude výsledek vždy 0,1 (nebo 1,0).
Sdílené a soukromé proměnné #include <stdio.h> #include <omp.h> Vlákno 0 Vlákno 1 void main() { int i, j; i = 10; #pragma omp parallel private(j) shared(i) { j = omp_get_thread_num(); printf("%i\n", i+j); }; } Čas i=10 j0=0 tisk i+j0 j1=1 tisk i+j1 Chci-li, aby k proměnné i měla přístup všechna vlákna, umístíme jí do sdílené paměti pomocí shared(i). Překladač pak nevytváří lokální kopii proměnné. Pozn. pokud neuvedeme ani private() ani shared(), překladač se snaží uhodnout, jaká je to proměnná. Tomuto hádání lze zamezit pomocí specifikace default(shared), default(private) či default(none).
Sdílené a soukromé proměnné #include <stdio.h> #include <omp.h> void main() { int sum_i=0; #pragma omp parallel reduction(+:sum_i) { sum_i += omp_get_thread_num(); } reduction hodnoty soukromé proměnné se na konci redukují do původní proměnné. printf("%d\n", sum_i); } Existují i další varianty specifikace proměnných, nejužitečnější však jsou: default(none), private, shared, reduction Pravidlo pro specifikaci proměnných: private: pomocné proměnné jejichž hodnota před a po paralelním bloku není důležitá, řídící proměnné cyklu, shared: proměnné jejichž hodnotu před či po par. bloku potřebujeme. Je třeba ohlídat, aby více CPU nezapisovalo na stejné místo!
Paralelizace cyklů Pomocí #pragma omp parallel nebo!$omp PARALLEL se program rozdělí na vlákna vykonávající tutéž činnost! Abychom rozdělili práci mezi více procesorů, můžeme použít v paralelním bloku #pragma omp for nebo!$omp DO. #include program pokus3 <stdio.h> integer :: i, sum void sum=0main() {!$omp int parallel i, sum=0; private(i) &!$omp #pragma reduction(+:sum) omp parallel private(i)\!$omp reduction(+:sum) do do { i = 0, 99 sum #pragma = sum omp + ifor end do for (i=0;i<100;i++) sum += i;!$omp } end parallel printf("%i\n", *, sum sum); } end program pokus3 sum=0 sum0=0 i0=0..49: sum0+=i0 sum=sum0+sum1 sum1=0 i1=50..99: sum1+=i1
Paralelizace cyklů Existuje i kompaktnější zápis: #omp parallel for!$omp PARALLEL DO Je možné určit, jak se budou kroky cyklu dělit mezi procesory: schedule(static)- všichni jsme si rovni schedule(dynamic)- kdo dřív příjde, ten dřív mele schedule(guided) - tomu dala, tomu míň, na maličkého se nedostalo Kompletní popis na www.openmp.org nebo na dalších seminářích.
Příklad překladače firmy Intel Překlad: přepínač -openmp ifort -O -openmp priklad1.f90 Spuštění na všech procesorech:./a.out Spuštení na 3 procesorech: export OMP_NUM_THREADS=3./a.out OMP_NUM_THREADS=3./a.out Poznámka: programy přeložené překladači od Intelu často vyžadují nastavení limitu pro zásobník, např. pomocí příkazu ulimit -s 256000
Příklad analýza skalárního kódu Nalezení míst, ve kterých se tráví nejvíce času profiler 1) překlad s požadavkem na vytvoření profilu: ifort -O -p program.f90 -o program 2) spuštění programu./program v pracovním adresáři vznikne soubor gmon.out 3) výpis informací o běhu programu gprof./program
Příklad analýza skalárního kódu Příklad: Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 50.78 113.49 113.49 100 1.13 1.13 fulltvd_mp_tvd_ 13.24 143.09 29.60 100 0.30 0.30 maccormack_mp_forward_ 13.19 172.57 29.48 100 0.29 0.29 maccormack_mp_backward_ 9.95 194.80 22.23 100 0.22 0.22 maccormack_mp_timestep_ 6.22 208.71 13.91 1 13.91 216.83 MAIN 1.86 212.86 4.15 cvtas_a_to_s 1.36 215.89 3.03 200 0.02 0.02 boundary_mp_bounds_ 1.11 218.36 2.47 200 0.01 0.01 viscousterm_mp_viscous_... kolik % času trávíme v podprogramu Má smysl paralelizovat fulltvd_mp_tvd, maccormack_mp_* Nemá cenu ztrácet čas s boundary_mp_bounds,...
Příklad paralelizace modulu FULLTVD!$omp parallel do default(none) &!$omp private(i,j,d,or2,s,s1,s2,s3,t1,t2,t3,q1,q2,q3,rho,ux,uy,uz) &!$omp private(h,uu,c,u1,u2,u3,aaa,nu,vabsnu,a_l,a_c,a_r,sb,phi) &!$omp shared(nk,ni,nj,si,cy,cz,w,u_x,u_y,u_z,p,rphi,vol,omega,gamma) do k = 1, NK do j = 1, NJ do i = 0, NI or2 = omega**2/2* & ((CY(i,j,k)+CY(i+1,j,k))**2+(CZ(i,j,k)+CZ(i+1,j,k))**2)/4 S = sqrt(si(i,j,k,1)**2+si(i,j,k,2)**2+si(i,j,k,3)**2) s1 = SI(i,j,k,1)/S s2 = SI(i,j,k,2)/S s3 = SI(i,j,k,3)/S... RPhi(i,j,k,1) = -rho/2/c*phi(1) + Phi(2) + rho/2/c*phi(5) RPhi(i,j,k,2) =... RPhi(i,j,k,3) =... RPhi(i,j,k,4) =... RPhi(i,j,k,5) =... end do end do end do Pentium D: #CPU Celý program TVD orig. 22.0 11.0 1 22.0 11.0 2 15.5 6.8 SGI Altix (load 22!): #CPU Celý program TVD 1 26.5 10.4 2 20.0 6.4 4 17.1 4.0