CUDA haladó ismeretek



Hasonló dokumentumok
Videókártya - CUDA kompatibilitás: CUDA weboldal: Példaterületek:

Párhuzamos és Grid rendszerek

8. gyakorlat Pointerek, dinamikus memóriakezelés

Magas szintű optimalizálás

GPGPU alapok. GPGPU alapok Grafikus kártyák evolúciója GPU programozás sajátosságai

C memóriakezelés. Mutató típusú változót egy típus és a változó neve elé írt csillag karakterrel hozhatjuk létre.

Mutatók és mutató-aritmetika C-ben március 19.

C programozási nyelv Pointerek, tömbök, pointer aritmetika

Készítette: Trosztel Mátyás Konzulens: Hajós Gergely

KÉPFELDOLGOZÓ ALGORITMUSOK FEJLESZTÉSE GRAFIKUS HARDVER KÖRNYEZETBEN

Visszalépéses keresés

CUDA alapok CUDA projektek. CUDA bemutató. Adatbányászat és Webes Keresés Kutatócsoport SZTAKI

egy szisztolikus példa

Alkalmazott modul: Programozás 4. előadás. Procedurális programozás: iteratív és rekurzív alprogramok. Alprogramok. Alprogramok.

1. Alapok. Programozás II

OpenCL - The open standard for parallel programming of heterogeneous systems

A verem (stack) A verem egy olyan struktúra, aminek a tetejéről kivehetünk egy (vagy sorban több) elemet. A verem felhasználása

GPGPU-k és programozásuk

Programozás alapjai. (GKxB_INTM023) Dr. Hatwágner F. Miklós augusztus 29. Széchenyi István Egyetem, Gy r

Felvételi vizsga mintatételsor Informatika írásbeli vizsga

Programozás I. 3. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

Felvételi tematika INFORMATIKA

Programozás alapjai. 10. előadás

SAT probléma kielégíthetőségének vizsgálata. masszív parallel. mesterséges neurális hálózat alkalmazásával

Programozas 1. Strukturak, mutatok

Algoritmusok és adatszerkezetek gyakorlat 06 Adatszerkezetek

Számítógépek felépítése

10. gyakorlat. Pointerek Tárolási osztályok

Programozás. (GKxB_INTM021) Dr. Hatwágner F. Miklós április 4. Széchenyi István Egyetem, Gy r

Hallgatói segédlet: Nvidia CUDA C programok debugolása Nvidia Optimus technológiás laptopokon. Készítette: Kovács Andor. 2011/2012 első félév

Szimuláció RICHARD M. KARP és AVI WIGDERSON. (Készítette: Domoszlai László)

Programozási segédlet

BME MOGI Gépészeti informatika 5.

Bevezetés a programozásba I 10. gyakorlat. C++: alprogramok deklarációja és paraméterátadása

Alkalmazott modul: Programozás 8. előadás. Strukturált programozás: dinamikus memóriakezelés. Dinamikus memóriakezelés. Dinamikus memóriakezelés

Programozás I. 1. előadás: Algoritmusok alapjai. Sergyán Szabolcs

Hatékony memóriakezelési technikák. Smidla József Operációkutatási Laboratórium január 16.

Programozás alapjai. 7. előadás

Occam 1. Készítette: Szabó Éva

Pénzügyi algoritmusok

B-fa. Felépítés, alapvető műveletek. Programozás II. előadás. Szénási Sándor.

C programozás. 6 óra Függvények, függvényszerű makrók, globális és

Láncolt listák. Egyszerű, rendezett és speciális láncolt listák. Programozás II. előadás. Szénási Sándor

1.1. A forrásprogramok felépítése Nevek és kulcsszavak Alapvető típusok. C programozás 3

SZÁMÍTÓGÉPEK BELSŐ FELÉPÍTÉSE - 1

Adatszerkezetek. Nevezetes algoritmusok (Keresések, rendezések)

6. gyakorlat Egydimenziós numerikus tömbök kezelése, tömbi algoritmusok

10. gyakorlat Struktúrák, uniók, típusdefiníciók

Készítette: Nagy Tibor István

A MATLAB alapjai. Kezdő lépések. Változók. Aktuális mappa Parancs ablak. Előzmények. Részei. Atomerőművek üzemtana

Adatszerkezetek Tömb, sor, verem. Dr. Iványi Péter

Bevezetés a programozásba I.

1. Template (sablon) 1.1. Függvénysablon Függvénysablon példányosítás Osztálysablon

Vezérlési szerkezetek

Egyszerű programozási tételek

Algoritmusok és adatszerkezetek 2.

A függvény kód szekvenciáját kapcsos zárójelek közt definiáljuk, a { } -ek közti részt a Bash héj kód blokknak (code block) nevezi.

// keressük meg a legnagyobb faktoriális értéket, ami kisebb, // mint százmillió

Az UPPAAL egyes modellezési lehetőségeinek összefoglalása. Majzik István BME Méréstechnika és Információs Rendszerek Tanszék

elektronikus adattárolást memóriacím

Programozás alapjai C nyelv 8. gyakorlat. Mutatók és címek (ism.) Indirekció (ism)

Mutatók és címek (ism.) Programozás alapjai C nyelv 8. gyakorlat. Indirekció (ism) Néhány dolog érthetőbb (ism.) Változók a memóriában

SzA19. Az elágazások vizsgálata

Alkalmazott modul: Programozás 10. fejezet. Strukturált programozás: dinamikus memóriakezelés. Giachetta Roberto

Java II. I A Java programozási nyelv alapelemei

3 A C programozási nyelv szintaktikai egységei

ELEMI PROGRAMOZÁSI TÉTELEK

Struktúra nélküli adatszerkezetek

Láncolt Listák. Adat1 Adat2 Adat3 ø. Adat1 Adat2 ø Adat3

Függvények. Programozás I. Hatwágner F. Miklós november 16. Széchenyi István Egyetem, Gy r

Tömbök kezelése. Példa: Vonalkód ellenőrzőjegyének kiszámítása

GPU Lab. 14. fejezet. OpenCL textúra használat. Grafikus Processzorok Tudományos Célú Programozása. Berényi Dániel Nagy-Egri Máté Ferenc

Programozás I. Egyszerű programozási tételek. Sergyán Szabolcs

Programozás I gyakorlat

Programozási nyelvek Java

Adatszerkezetek I. 8. előadás. (Horváth Gyula anyagai felhasználásával)

Rekurzió. Dr. Iványi Péter

Bevezetés a programozásba. 5. Előadás: Tömbök

Pásztor Attila. Algoritmizálás és programozás tankönyv az emeltszintű érettségihez

Számítógép felépítése

Programozás alapjai II. (7. ea) C++ Speciális adatszerkezetek. Tömbök. Kiegészítő anyag: speciális adatszerkezetek

7. fejezet: Mutatók és tömbök

Programozás I. Matematikai lehetőségek Műveletek tömbökkel Egyszerű programozási tételek & gyakorlás V 1.0 OE-NIK,

Speciális adatszerkezetek. Programozás alapjai II. (8. ea) C++ Tömbök. Tömbök/2. N dimenziós tömb. Nagyméretű ritka tömbök

Operációs rendszerek. Az NT folyamatok kezelése

Kiegészítő részelőadás 1. Az algoritmusok hatékonyságának mérése

A C programozási nyelv III. Pointerek és tömbök.

GPU-Accelerated Collocation Pattern Discovery

Programozás I. Egyszerű programozási tételek. Sergyán Szabolcs

Miről lesz ma szó? A PROGAMOZÁS ALAPJAI 1. Dinamikus változók. Dinamikus változók. Dinamikus változók. Dinamikus változók. 7.

Készítette: Nagy Tibor István

Mintavételes szabályozás mikrovezérlő segítségével

Flynn féle osztályozás Single Isntruction Multiple Instruction Single Data SISD SIMD Multiple Data MISD MIMD

Programozás alapjai II. (7. ea) C++

Generikus osztályok, gyűjtemények és algoritmusok

1. Jelölje meg az összes igaz állítást a következők közül!

Programozási technológia

Egész számok. pozitív egész számok: 1; 2; 3; 4;... negatív egész számok: 1; 2; 3; 4;...

4. Funkcionális primitívek GPUn

Hatékonyság 1. előadás

Átírás:

CUDA haladó ismeretek CUDA környezet részletei Többdimenziós indextér használata Megosztott memória használata Atomi műveletek használata Optimalizálás Hatékonyság mérése Megfelelő blokkméret kiválasztása Szenasi.sandor@nik.uni-obuda.hu

CUDA haladó ismeretek CUDA környezet részletei Többdimenziós indextér használata Megosztott memória használata Atomi műveletek használata Optimalizálás Hatékonyság és mérése Megfelelő blokkméret kiválasztása Szenasi.sandor@nik.uni-obuda.hu

Mátrix szorzó alkalmazás elkészítése Feladat 3.1 Készítsünk CUDA alkalmazást, amelyik kétdimenziós (NxN méretű, egyszeres lebegőpontos számokat tartalmazó) mátrixok szorzását tudja elvégezni a GPU segítségével: A kód tartalmazzon egy N konstanst, ami a mátrixok méretét tartalmazza Foglaljon le 3 darab NxN méretű mátrixot (A, B, C) Töltse fel az A mátrixot tetszőleges adatokkal (pl. a i,j = i + j) Töltse fel a B mátrixot tetszőleges adatokkal (pl. b i,j = i - j) Foglaljon le 3 darab NxN méretű memóriaterületet a grafikus kártya memóriájában (deva, devb, devc) Végezze el a bemenő adatok másolását: A deva, B devb Indítson el egy kernelt, ami kiszámítja a devc = deva * devb eredményt Végezze el a kimenő adatok másolását: devc C Írja ki az így kapott C vektor elemeinek értékét a képernyőre 3

Többdimenziós mátrix a memóriában Többdimenziós mátrix lineáris memória Bár a C programunkban tudunk többdimenziós tömböket lefoglalni, ezek elemei a memóriában értelemszerűen lineárisan helyezkednek el Egy egyszerű 4x4-es mátrix elhelyezkedése: A mátrix a 0,0 a 0,1 a 0,2 a 0,3 A = a 0,0 A kétdimenziós tömb a 1,0 a 1,1 a 1,2 a 1,3 a 2,0 a 2,1 a 2,2 a 2,3 a 3,0 a 3,1 a 3,2 a 3,3......... a 0,0 a 0,1 a 0,2 a 0,3 a 1,0 a 1,1 a 1,2 a 1,3 a 2,0 a 2,1 a 2,2 a 2,3 a 3,0 a 3,1 a 3,2 a 3,3........................ Többdimenziós mátrix elemeihez való hozzáférés Amennyiben tehát ismert a mátrix kezdőcíme, a dimenziói, illetve az elemek méretei, a mátrix bármelyik elemének címe egyszerűen kiszámítható: a sor,oszlop = a 0,0 + (sor * oszlopszám + oszlop) * elemméret Mivel a CUDA kernelnek csak a mátrixokat tartalmazó memóriaterületek kezdőcímét adjuk át, a fenti képlettel érhetjük el az egyes elemeket 4

Többdimenziós mátrix a memóriában Blokkok használata Többdimenziós mátrixok esetén már egy 30x30-as méret is 900 darab szálat igényelne, amit nem tudunk egy blokkban elhelyezni Emiatt célszerű a programot tetszőleges méretű blokkok kezelésére felkészíteni, ennek megfelelően a blokk azonosítót is kezelni kell Egy lehetséges kernel devc = deva * devb mátrixszorzás kódja: 1 2 3 4 5 6 7 8 9 10 11 12 global static void MatrixMul(float *deva, float *devb, float *devc) { int indx = blockidx.x * blockdim.x + threadidx.x; int indy = blockidx.y * blockdim.y + threadidx.y; } if (indx < N && indy < N) { float sum = 0; for(int i = 0; i < N; i++) { sum += deva[indy * N + i] * devb[i * N + indx]; } devc[indy * N + indx] = sum; } 5

Többdimenziós mátrix a memóriában Hoszt oldali kód Inicializálás, memóriaterületek lefoglalása 1 2 3 4 5 Bemenő adatok átmásolása 6 7 cudamemcpy(deva, A, sizeof(float) * N * N, cudamemcpyhosttodevice); cudamemcpy(devb, B, sizeof(float) * N * N, cudamemcpyhosttodevice); Kernel indítása 8 9 10 11 dim3 grid((n - 1) / BlockN + 1, (N - 1) / BlockN + 1); dim3 block(blockn, BlockN); MatrixMul<<<grid, block>>>(deva, devb, devc); cudathreadsynchronize(); Eredmények másolása, memória felszabadítása 12 13 cudasetdevice(0); float A[N][N], B[N][N], C[N][N]; float *deva, *devb, *devc; cudamalloc((void**) &deva, sizeof(float) * N * N); cudamalloc((void**) &devb, sizeof(float) * N * N); cudamalloc((void**) &devc, sizeof(float) * N * N); cudamemcpy(c, devc, sizeof(float) * N * N, cudamemcpydevicetohost); cudafree(deva); cudafree(devb); cudafree(devc); verzió 2010.07.20. 6 Szenasi.sandor@nik.uni-obuda.hu

Igazított elhelyezés Többdimenziós mátrix igazított tárolása Néha célszerű elválasztani egymástól a mátrix egy sorában található oszlopok számát és a memóriában tárolt tömb sorainak tényleges méretét (pl. kettes számrendszerbeli számokkal gyorsabban számítható a szorzása, illetve a CUDA végrehajtó egységek memóriakezelése is gyorsítható így) Egy egyszerű 5x5-ös mátrix elhelyezkedése, 8 elemes igazítással: A mátrix a 0,0 a 0,1 a 0,2 a 0,3 a 0,4 A = a 0,0 A kétdimenziós tömb a 1,0 a 1,1 a 1,2 a 1,3 a 1,4 a 2,0 a 2,1 a 2,2 a 2,3 a 2,4 a 3,0 a 3,1 a 3,2 a 3,3 a 3,4 a 4,0 a 4,1 a 4,2 a 4,3 a 4,4......... a 0,0 a 0,1 a 0,2 a 0,3 a 0,4......... a 1,0 a 1,1 a 1,2 a 1,3 a 1,4......... a 2,0 a 2,1 a 2,2 a 2,3 a 2,4......... a 3,0 a 3,1 a 3,2 a 3,3 a 3,4......... a 4,0 a 4,1 a 4,2 a 4,3 a 4,4........................ Igazított tárolás esetén a hozzáférés A képlet hasonló, csak nem a tényleges oszlopszámmal szorzunk: a sor,oszlop = a 0,0 + (sor * igazított_oszlopszám + oszlop) * elemméret 7

Igazított memóriakezelés Memória lefoglalása A CUDA osztálykönyvtár rendelkezik egy memóriafoglaló rutinnal, ami automatikusan a legoptimálisabb igazítással foglalja le a megadott kétdimenziós memóriaterületet: cudamallocpitch(void** devptr, size_t *pitch, size_t width, size_t height) devptr lefoglalt memóriára mutató pointer (címszerinti paraméter) pitch igazítás mértéke (címszerinti paraméter) width mátrix egy sorának a mérete height mátrix sorainak száma A lineáris foglaláshoz hasonlóan a devptr tartalmazza majd a memóriacímet Az igazítás méretét tehát nem a függvény hívásakor kell megadni, hanem azt az osztálykönyvtár fogja megállapítani, és az így kiszámított értéket fogja visszaadni a pitch paraméterként átadott változóban A mátrix egy sorának méreténél (width) ügyeljünk rá, hogy bájtban kell megadni a méretet, hiszen a függvény nem tudhatja, hogy mekkora elemeket akarunk majd eltárolni 8

Igazított memóriakezelés Memória másolás Az igazított memóriakezelés során nem használható az egyszerű lineáris memória másolás, hiszen a cél és a forrás igazítása különböző is lehet A másolásra jól használható az alábbi függvény, ami kezeli ezt a problémát: cudamemcpy2d(void* dst, size_t dpitch, const void* src, size_t spitch, size_t width, size_t height, enum cudamemcpyking kind) dst másolás célját mutató pointer dpitch a cél adatszerkezet által használt igazítás mérete src másolás forrását mutató pointer spitch a forrás adatszerkezet által használt igazítás mérete width a 2 dimenziós adatszerkezet egy sorának a mérete (bájtban) heigh a 2 dimenziós adatszerkezet sorainak a száma Kind másolás iránya (értékei azonosak a lineáris másolásnál látottakkal) hoszt hoszt (cudamemcpyhosttohost) hoszt eszköz (cudamemcpyhosttodevice) eszköz hoszt (cudamemcpydevicetohost) eszköz eszköz (cudamemcpydevicetodevice) Amennyiben egy egyszerű igazítás nélküli tömböt használunk, akkor ne felejtsük el, hogy az igazítás mérete = tömb sorának mérete (és nem 0) 9

Mátrix szorzó alkalmazás elkészítése Feladat 3.2 Készítsünk CUDA alkalmazást, amelyik kétdimenziós (NxN méretű, egyszeres lebegőpontos számokat tartalmazó) mátrixok szorzását tudja elvégezni a GPU segítségével: A kód tartalmazzon egy N konstanst, ami a mátrixok méretét tartalmazza Foglaljon le 3 darab NxN méretű mátrixot (A, B, C) Töltse fel az A mátrixot tetszőleges adatokkal (pl. a i,j = i + j) Töltse fel a B mátrixot tetszőleges adatokkal (pl. b i,j = i - j) Foglaljon le 3 darab NxN méretű memóriaterületet a grafikus kártya memóriájában a kártya által választott igazítással (deva, devb, devc) Végezze el a bemenő adatok másolását: A deva, B devb Indítson el egy kernelt, ami kiszámítja a devc = deva * devb eredményt Végezze el a kimenő adatok másolását: devc C Írja ki az így kapott C vektor elemeinek értékét a képernyőre 10

Kernel igazított memóriakezeléssel Módosítások A kernel a paraméterként átadott igazítás mérettel végzi a szorzást N helyett Az igazítás mérete bájtban van megadva, így típusos mutatók esetén a értékét osztani kell az elem méretével (a példában feltételeztük hogy ez megtehető, amennyiben a igazítás nem lenne osztható az elemmérettel, akkor pl. típus nélküli mutatókkal tudjuk az elemek helyeit kiszámítani) Egy lehetséges kernel devc = deva * devb mátrixszorzás kódja: 1 2 3 4 5 6 7 8 9 10 11 12 global static void MatrixMul(float *deva, float *devb, float *devc, size_t pitch) { int indx = blockidx.x * blockdim.x + threadidx.x; int indy = blockidx.y * blockdim.y + threadidx.y; } if (indx < N && indy < N) { float sum = 0; for(int i = 0; i < N; i++) { sum += deva[indy * pitch/sizeof(float) + i] * devb[i * pitch/sizeof(float) + indx]; } devc[indy * pitch/sizeof(float) + indx] = sum; } 11

Kernel igazított memóriakezeléssel Hoszt oldali kód 6 7 12 13 Inicializálás, memóriaterületek lefoglalása 1 2 3 4 5 Bemenő adatok átmásolása (feltételezzük, hogy a pitch azonos lett) cudamemcpy2d(deva, pitch, A, sizeof(float) * N, sizeof(float) * N, N, cudamemcpyhosttodevice); cudamemcpy2d(devb, pitch, B, sizeof(float) * N, sizeof(float) * N, N, cudamemcpyhosttodevice); Kernel indítása 8 9 10 11 cudasetdevice(0); float A[N][N], B[N][N], C[N][N]; float *deva, *devb, *devc; size_t pitch; cudamallocpitch((void**) &deva, &pitch, sizeof(float) * N, N); cudamallocpitch((void**) &devb, &pitch, sizeof(float) * N, N); cudamallocpitch((void**) &devc, &pitch, sizeof(float) * N, N); dim3 grid((n - 1) / BlockN + 1, (N - 1) / BlockN + 1); dim3 block(blockn, BlockN); MatrixMul<<<grid, block>>>(deva, devb, devc, pitch); cudathreadsynchronize(); Eredmények másolása, memória felszabadítása cudamemcpy2d(c, sizeof(float) * N, devc, pitch, sizeof(float) * N, N, cudamemcpydevicetohost); cudafree(deva); cudafree(devb); cudafree(devc); verzió 2010.07.20. Szenasi.sandor@nik.uni-obuda.hu 12

CUDA haladó ismeretek CUDA környezet részletei Többdimenziós indextér használata Megosztott memória használata Atomi műveletek használata Optimalizálás Hatékonyság és mérése Megfelelő blokkméret kiválasztása Szenasi.sandor@nik.uni-obuda.hu

Megosztott memória kezelése Előző megoldások gyengesége A mátrix szorzás meglehetősen kevés aritmetikai műveletet tartalmaz a memóriamozgatások számához képest A GPU a memóriakezelésből származó várakozási időket nem gyorsítótárral, hanem a végrehajtóegységek átütemezésével próbálja elrejteni. Ha azonban nincs végrehajtandó művelet, akkor ezt nem tudja megoldani A CUDA környezetben megvalósított algoritmusok jóságának összehasonlítására emiatt gyakran használatos az aritmetikai műveletek száma/memóriahozzáférések száma hányados Megoldási lehetőségek Párhuzamosság növelése, amennyiben a feladat ezt lehetővé teszi (jelen esetben erre nem látunk lehetőséget) Memóriahozzáférések számának csökkentése (tulajdonképpen programozott gyorsítótár kezelés) minél több változó regiszterekben tartása megosztott memóriaterület használata Egyéb megoldások keresése, esetenként az algoritmusok áttervezése 14

Ötlet a mátrix szorzás gyorsítására Csempe technika Mivel a bemenő mátrixok egyes elemei több kimenő elem számításához is szükségesek, így a nem optimalizált változatban több szál is betölti őket egymástól függetlenül Célszerű lenne ezen szálak működését összehangolni: a teljes kimenő mátrixot régiókra bontjuk (csempe) a régióknak megfelelő méretű megosztott memóriaterületet lefoglalunk a két bemenő mátrix elemeinek ideiglenes betöltéséhez a régiókban található minden szál betölti az ő poziciójának megfelelő elemet a két bemenő mátrixból a megosztott memóriába a szál a saját és a többi szál által betöltött adatok segítségével kiszámítja az eredmény mátrix rá eső értékét A megosztott memória mérete meglehetősen korlátos, emiatt elképzelhető, hogy a fenti műveletet csak több lépésben, a bemenő mátrixokat több csempével lefedve, majd ezek értékét összeadva lehet végrehajtani Ez utóbbi esetben lényeges a szinkronizálás is, hogy egyik szál se kezdje el betölteni a következő csempe adatait, miközben a vele egy régióban lévő szálak még dolgoznak a részeredményeken 15

Mátrix szorzó alkalmazás elkészítése Feladat 3.3 Készítsünk CUDA alkalmazást, amelyik kétdimenziós (NxN méretű, egyszeres lebegőpontos számokat tartalmazó) mátrixok szorzását tudja elvégezni a GPU segítségével: A kód tartalmazzon egy N konstanst, ami a mátrixok méretét tartalmazza Foglaljon le 3 darab NxN méretű mátrixot (A, B, C) Töltse fel az A mátrixot tetszőleges adatokkal (pl. a i,j = i + j) Töltse fel a B mátrixot tetszőleges adatokkal (pl. b i,j = i - j) Foglaljon le 3 darab NxN méretű memóriaterületet a grafikus kártya memóriájában (deva, devb, devc) Végezze el a bemenő adatok másolását: A deva, B devb Indítson el egy kernelt, ami kiszámítja a devc = deva * devb eredményt, az előzőleg megismert csempe technika segítségével Végezze el a kimenő adatok másolását: devc C Írja ki az így kapott C vektor elemeinek értékét a képernyőre 16

Mátrix szorzás gyorsítása As Bs c B 1. Csempékre bontás 3x3 db régió, ezekben 3x3 db szál 2. Minden szál átmásol egy-egy elemet a megosztott memóriába 3. Szinkronizáció A Globális memória 17 C

Mátrix szorzás gyorsítása Szál 0,0 As Bs c B 4. Minden szál a saját részeredményét kiszámolja a megosztott memória tartalma alapján 5. Szinkronizáció A C Globális memória 18

Szál 0,0 + Mátrix szorzás gyorsítása As Bs c B 6. Következő csempék adatainak betöltése 7. Szinkronizálás 8. Szálak újra elvégzik a szorzást. A szorzatot hozzáadják az előző részeredményhez 9. Szinkronizálás A Globális memória 19 C

Szál 0,0 + Mátrix szorzás gyorsítása As Bs c B 6. Következő csempék adatainak betöltése 7. Szinkronizálás 8. Szálak újra elvégzik a szorzást. A szorzatot hozzáadják az előző részeredményhez 9. Szinkronizálás A Globális memória 20 C

Mátrix szorzás gyorsítása As Bs c B 10. Minden szál az általa kiszámított elemet bemásolja a C-be 11. Az összes blokk és A szál lefutása után az eredmény mátrix minden eleme a helyére kerül Globális memória 21 C

Optimalizált mátrix szorzás kernel A kernel meghívása és az adatok másolása megegyezik az első mátrix szorzásnál megismerttel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 global static void MatrixMul(float *deva, float *devb, float *devc) { shared float Ashared[BlockN][BlockN]; shared float Bshared[BlockN][BlockN]; } int indx = blockidx.x * blockdim.x + threadidx.x; int indy = blockidx.y * blockdim.y + threadidx.y; float c = 0; for(int k = 0; k < N / BlockN; k++) { Ashared[threadIdx.y][threadIdx.x] = deva[k * BlockN + threadidx.x + indy * N]; Bshared[threadIdx.y][threadIdx.x] = devb[indx + (k * BlockN + threadidx.y) * N]; syncthreads(); for(int i = 0; i < BlockN; i++) { c += Ashared[threadIdx.y][i] * Bshared[i][threadIdx.x]; } syncthreads(); } devc[indx + indy * N] = c; 22

Összehasonlító vizsgálat Eredeti és optimalizált kód összehasonlítása Vízszintes tengely: mátrix mérete (N) Függőleges tengely: futási idő (másodperc) 1 0,9 0,8 0,7 0,6 0,5 0,4 Eredeti Optimalizált 0,3 0,2 0,1 0 40 80 120 160 200 23

CUDA haladó ismeretek CUDA környezet részletei Többdimenziós indextér használata Megosztott memória használata Atomi műveletek használata Optimalizálás Hatékonyság és mérése Megfelelő blokkméret kiválasztása Szenasi.sandor@nik.uni-obuda.hu

Atomi műveletek szükségessége Atomiság igénye Az adatpárhuzamos feladatok készítésekor ideális esetben a végeredmény kiszámításához nincs szükség az indextér egymástól független pontjain végzett műveletekre (pl. két mátrix összeadása), esetleg csak az olvasásra van szükség (pl. két mátrix szorzása) Bizonyos feladatoknál azonban ez nem kerülhető el, mint például a gyakori aggregációs műveleteknél: adatszerkezet elemeinek az összege/átlaga adatszerkezet legkisebb/legnagyobb eleme adatszerkezetben megadott tulajdonságú elemek száma stb. Lehetséges megoldások Ezen feladatok egy része egyszerűen megoldható adatpárhuzamos környezetben is (pl. van-e T tulajdonságú elem) A feladatok egy része azonban csak (részben) szekvenciális megoldással oldható meg (pl. legkisebb elem megkeresése) 25

CUDA atomi műveletek CUDA lehetőségek A CUDA által nyújtott atomi műveletek segítenek kiküszöbölni a versenyhelyzet okozta problémákat. A hardver garantálja, hogy az így elvégzett műveletek a szálak számától függetlenül szekvenciálisan fognak végrehajtódni (vagy legalábbis úgy tűnnek) Operandusok lehetnek Változó a globális memóriában Változó a megosztott memóriában Operandusok mérete 32 bites egész (1.1 számítási képesség) 64 bites egész (1.2 számítási képesség) Teljesítmény megfontolások Az atomi műveletek jellegüknél fogva nem párhuzamosíthatók, ennek megfelelően jelentősen rontják minden kernel teljesítményét Bizonyos feladatoknál azonban nem lehet őket teljesen kiküszöbölni, azonban ilyenkor is célszerű esetleg előzetes párhuzamos feldolgozásokkal csökkenteni a szükséges atomi műveletek számát 26

CUDA atomi műveletek Aritmetikai függvények Az atomi műveletek egyik paramétere általában egy memóriacím (globális vagy megosztott memóriában), második paramétere pedig egy egész szám int atomicadd(int* address, int val) Az első paraméterként címszerint átadott változó értékéhez hozzáadja a második paraméterben átadott értéket. Visszatérési értéke az eredeti szám int atomicsub(int* address, int val) Az első paraméterként címszerint átadott változó értékéből kivonja a második paraméterben átadott értéket. Visszatérési értéke az eredeti szám int atomicexch(int* address, int val); Az első paraméterként címszerint átadott változónak értékül adja a második paraméterként átadott értéket. Visszatérési értéke az eredeti szám int atomicmin(int* address, int val); Ha a második paraméterként átadott szám értéke kisebb mint az első címszerinti paraméterként átadott változó értéke, akkor ez utóbbit felülírja az előbbivel. Visszatérési értéke az eredeti szám 27

CUDA atomi műveletek Aritmetikai függvények folyt. int atomicmax(int* address, int val); Ha a második paraméterként átadott szám értéke nagyobb, mint az első címszerinti paraméterként átadott változó értéke, akkor ez utóbbit felülírja az előbbivel. Visszatérési értéke az eredeti szám unsigned int atomicinc(unsigned int* address, unsigned int val) Ha az első paraméterként címszerint átadott változó értéke kisebb, mint a második paraméter, akkor az előbbi értékét növeli 1-el, különben 0-t ad neki értékül. Visszatérési értéke az eredeti szám unsigned int atomicdec(unsigned int* address, unsigned int val) Ha az első paraméterként címszerint átadott változó értéke nagyobb, mint 0, akkor az előbbi értékét növeli 1-el, különben a második paraméterben átadott értéket másolja bele. Visszatérési értéke az eredeti szám int atomiccas(int* address, int compare, int val) Ha az elő paraméterként címszerint átadott változó értéke egyenlő a második paraméterrel, akkor az előbbinek értékül adja a harmadik paraméter értékét. Visszatérési értéke az eredeti érték 28

CUDA atomi műveletek Logikai függvények int atomicand(int* address, int val) Az első paraméterként címszerint átadott változó értékéül adja a második változóval vett ÉS művelet eredményét. Visszatérési értéke az eredeti szám int atomicor(int* address, int val) Az első paraméterként címszerint átadott változó értékéül adja a második változóval vett VAGY művelet eredményét. Visszatérési értéke az eredeti szám int atomicxor(int* address, int val) Az első paraméterként címszerint átadott változó értékéül adja a második változóval vett KIZÁRÓVAGY művelet eredményét. Visszatérési értéke az eredeti szám Feladat 3.4 Készítsünk CUDA alkalmazást, amelyik egy (hoszt oldalon) véletlenszerű számokkal feltöltött vektorból kiválasztja a minimális értéket. A feladat megoldása során használja a megismert atomi műveleteket 29

Vektor legkisebb elemének értéke Megoldás a globális memória használatával Az alap megoldás meglehetősen egyszerű, minden szál az általa indexelt elemre és a vektor első elemére meghívja a minimumot értékül adó függvényt A kernel lefutását követően tehát a vektor legelső eleme tartalmazza a legkisebb elem értékét 1 2 3 4 global static void MinSearch(float *deva) { int indx = blockidx.x * blockdim.x + threadidx.x; atomicmin(deva, deva[indx]); } Érdemes megfigyelni, hogy a kernel eleve több blokkos indításra lett tervezve, tehát az atomi műveletek nem csak blokkon belül, hanem blokkok között is biztosítják a versenyhelyzetek megoldását Feladat 3.5 Sejthető, hogy a globális memóriában végzett atomi műveletek meglehetősen lassúak, ezért próbáljunk egy olyan változatot, amelyik először a blokk minimumát keresi meg, majd ezzel számol globális minimumot 30

Memóriahozzáférések csökkentése Megosztott memória használata A blokkon belül az első szál feladata, hogy beállítsa a lokális minimumot tároló változó kezdőértékét Ezt követően az összes szál elvégzi a hozzá rendelt értékkel a minimum ellenőrzést Végül az első szál az így kiszámított lokális minimummal elvégzi a globális minimum ellenőrzést is Szintén ügyeljünk a szinkronizációs pontokra, nehogy rossz sorrendben fussanak le az utasítások az egyes szálakban 1 2 3 4 5 6 7 8 9 global static void MinSearch(float *deva) { shared int localmin; int indx = blockidx.x * blockdim.x + threadidx.x; if (threadidx.x == 0) localmin = deva[blockidx.x * blockdim.x]; syncthreads(); atomicmin(&localmin, deva[indx]); syncthreads(); if (threadidx.x == 0) atomicmin(deva, localmin); } 31

Összehasonlító vizsgálat Eredeti és optimalizált kód összehasonlítása Vízszintes tengely: vektor mérete (N) Függőleges tengely: futási idő (másodperc) 10 9 8 7 6 5 4 Eredeti Optimalizált 3 2 1 0 5000 10000 20000 30000 40000 verzió 2010.07.20. Szenasi.sandor@nik.uni-obuda.hu 32

Blokkon belüli párhuzamosítás Atomi műveletek teljesítménye Célszerű lenne a megoldást olyan formában keresni, hogy kihasználhassuk a párhuzamosságból eredő előnyöket is: blokkon belül is kisebb részfeladatokra kell bontani a minimum kiválasztást Párhuzamosított változat A megosztott memóriába betöltjük a globális memória egy szeletét: minden szál beolvassa a globális memória két elemét, majd ezek közül a kisebbiket eltárolja a megosztott memóriában Blokkon belül minden szál összehasonlít két elemet a megosztott memóriában, majd a kisebbiket eltárolja az alacsonyabb indexű helyen Következő iterációban már csak az előzőleg kisebbnek talált elemeket hasonlítják össze, így a vizsgálandó elemek száma mindig feleződik Miután megvan a blokk legkisebb eleme, az előzőleg megismert módon eldöntjük, hogy ezek közül melyik a globális minimum Feladat 3.6 Készítsük el a minimum kiválasztás fent leírt algoritmussal működő változatát 33

Megosztott memória Globális memória Párhuzamos minimum - betöltés 0 Egy példa N = 24 BlokkN = 4 (szálak száma) Minden blokk lefoglal BlokkN*2 méretű megosztott memóriát Minden blokk szálai BlokkN*4 méretű globális memóriából töltik be páronként a kisebb értékeket A blokkméret legyen 2 hatványa, így az üres helyeket feltöltjük: páratlanul maradt elem vektor bármelyik eleme Betöltés után szinkronizáció Blokk 1 Blokk 0 Min( A 0, A 1 ) Sz0 Min( A 2, A 3 ) Sz1 Min( A 4, A 5 ) Sz2 Min( A 6, A 7 ) Sz3 Min( A 8, A 9 ) Sz0 Min( A 10, A 11 ) Sz1 Min( A 12, A 13 ) Sz2 Min( A 14, A 15 ) Sz3 Min( A 16, A 17 ) Sz0 Min( A 18, A 19 ) Sz1 Min( A 20, A 21 ) Sz2 Min( A 22, A 23 ) Sz3 A 24 Sz0 A 0 Sz1 A 0 Sz2 A 0 Sz3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 34

Párhuzamos minimum blokk minimuma S 0 S 1 S 2 S 3 S 4 S 5 S 6 S 7 Min(S 0,S 1 ) Min(S 2,S 3 ) Min(S 4,S 5 ) Min(S 6,S 7 ) S 4 S 5 S 6 S 7 Min(S 0,S 1, S 2, S 3 ) Min(S 4,S 5, S 6, S 7 ) Min(S 4,S 5 ) Min(S 6,S 7 ) S 4 S 5 S 6 S 7 Blokk minimum Min(S 4,S 5, S 6, S 7 ) Min(S 4,S 5 ) Min(S 6,S 7 ) S 4 S 5 S 6 S 7 Minden szál log 2 BlokkN darab iterációt hajt végre, amely során az x indexű szál elvégzi az alábbi műveletet: S x = Min(S 2x, S 2x+1 ) A műveletek végén a vektor legelső eleme tartalmazza a blokk által feldolgozott elemek közüli legkisebb értékét Ezek közül kiválaszhatjuk a globális legkisebb elemet: atomi műveletek használatával a blokkok által kigyűjtött minimális elemeket egy másik vektorban gyűjtjük, majd ezekre ismét lefuttatjuk a fenti minimumkiválasztást (nagy elemszám esetén már érdemes lehet ezt a változatot használni) 35

Párhuzamos minimum - kernel Adatok betöltése globális memóriából (túlcímzés figyeléssel) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 global static void MinSearch(int *deva) { shared int localmin[blockn*2]; int blocksize = BlockN; int itemc1 = threadidx.x * 2; int itemc2 = threadidx.x * 2 + 1; for(int k = 0; k <= 1; k++) { int blockstart = blockidx.x * blockdim.x * 4 + k * blockdim.x * 2; int loadindx = threadidx.x + blockdim.x * k; if (blockstart + itemc2 < N) { int value1 = deva[blockstart + itemc1]; int value2 = deva[blockstart + itemc2]; localmin[loadindx] = value1 < value2? value1 : value2; } else if (blockstart + itemc1 < N) localmin[loadindx] = deva[blockstart + itemc1]; else localmin[loadindx] = deva[0]; } syncthreads(); 36

Párhuzamos minimum - kernel Lokális, majd globális minimum kiválasztása 21 22 23 24 25 26 27 28 29 } while (blocksize > 0) { int locmin = localmin[itemc1] < localmin[itemc2]? localmin[itemc1] : localmin[itemc2]; syncthreads(); localmin[threadidx.x] = locmin; syncthreads(); blocksize = blocksize / 2; } if (threadidx.x == 0) atomicmin(deva, localmin[0]); Megjegyzések A blokkméret mindenképpen 2 hatványa legyen A k ciklus szerepe, hogy minden blokk kétszer futtassa le a globális memóriából megosztott memóriába való másolást. Ennek következtében a BlokkN darab szálat tartalmazó blokk 4*BlokkN elemet tölt be a globális memóriából, ebből 2*BlokkN elemet (páronként a kisebbet) ment el a megosztott memóriába, így a fenti ciklus első iterációjában mind a BlokkN darab szál össze tud hasonlítani egy párt 37

Összehasonlító vizsgálat Első és második optimalizált kód összehasonlítása Vízszintes tengely: vektor mérete (N) Függőleges tengely: futási idő (másodperc) 0,3 0,25 0,2 0,15 Optimalizált 1 Optimalizált 2 0,1 0,05 0 5000 10000 20000 30000 40000 verzió 2010.07.20. Szenasi.sandor@nik.uni-obuda.hu 38

Összehasonlító vizsgálat Egyszerű CPU GPU kód összehasonlítása Vízszintes tengely: vektor mérete (N) Függőleges tengely: futási idő (másodperc) 0,6 0,5 0,4 0,3 CPU Optimalizált 2 0,2 0,1 0 10000 50000 100000 150000 200000 verzió a mérés során nem vettük figyelembe a bemenő adatok átmásolását a grafikus kártya memóriájába! 2010.07.20. Szenasi.sandor@nik.uni-obuda.hu 39

Irodalomjegyzék címsora [1] David B. Kirk, Wen-mei W. Hwu: Programming Massively Parallel Processors Elsevier 978-0-12-381472-2 http://www.elsevierdirect.com/morgan_kaufmann/kirk/ Angol 258 o. [2] NVIDIA CUDA Programming Guide 3.0 CUDA környezet teljes leírása (referencia, mintapéldák) http://developer.download.nvidia.com/compute/cuda/3_0/toolkit/docs/n VIDIA_CUDA_ProgrammingGuide.pdf szerző, intézmény, évszám verzió dátum/idő em@il 40