Párhuzamos programozás a gyakorlatban Tódor Balázs cougar@sch.bme.hu
Tematika Erről lesz szó 1 PC C++ OpenMP, thread-ek Közös memória Használati technikák, trükkök Erről nem Sok PC (grid, cluster) C#, Java, Python MPI, process-ek Elosztott memóriás modell Mi a mutex, CS, stb.
Tartalom Párhuzamosítás 1x1 Eszközök, technikák FP, AP, kommunikáció Tervezés Az eddigiek használata Top-down/Bottom-up Egy lehetséges jövő Stream programming
Miért? Hány processzorod van? A GPU számít? 1 CPU 2, 4, 8, mag Moore törvénye: 1,5 évente x2 mag Kihasználtság 50%, 25%, 13%, Core i7 = 8 mag
Párhuzamosítás 1x1 Független feladatok azonosítása Megjelenítés + számolás Zene + kezelőfelület Beolvasás + helyesírás Külön szál minden feladatnak Eszközök: Intel TBB, boost Kölcsönös kizárás printf Kommunikáció teljes függetlenség nem szokott lenni
Zab és bab Független feladatok Zab Bab Két külön szál Eszközök Boost::thread OpenMP Válogatás Zabmosás Babmosás Hegyezés Hámozás Csomagolás Csomagolás Vége
Boost STL++ Platformfüggetlen, ingyenes Mit tud? Array, circular buffer, bitset Graph, FSM File system, függvénypointerek Thread, mutex, stb. Any, variant, konvertálás String algoritmusok
Boost::thread Thread Ctor Join ThreadFunc Függvény N paraméter boost::thread zabthread(zabfunc); Zabmosás Hegyezés Válogatás boost::thread babthread(babfunc); Babmosás Hámozás Csomagolás Csomagolás zabthread.join(); babthread.join(); Vége
OpenMP Fordítóba beépített Intel, MSVC, gcc Minimális kódmódosítás #pragma omp Multiplatform Linux, Win32, stb. Rugalmas AP, FP, mutex, CS, single/master, stb.
OpenMP Parallel OMP engedélyezése Sections Párhuzamos blokk Section Szálak létrehozása Barrier Join_all omp section omp parallel omp sections omp barrier Zabmosás Hegyezés Csomagolás Válogatás omp section Vége Babmosás Hámozás Csomagolás
OpenMP vs boost
OpenMP
boost
Funkcionális párhuzamosítás Független funkciók Kölcsönös kizárás Kommunikáció... egyszerű, gyors, rugalmatlan 2x annyi szál?
Fizikai szimuláció dspacecollide NearCallback Step Ütköztetés 1. NxN R: objektumok Ütköztetés 2. NxN R: objektumpár W: contact-ok Feloldás N R: objektum W: objektum
Fizikai szimuláció Feladatok dspacecollide: (1*N)*N NearCallback: (1*N)*N Step (1*N) Független feladatok (collide+near+step) (collide+near+step) (collide+near+step)... Azonos funkció, sok szál Rugalmas, egyszerű De: csak független adatokon
Parallel for fork Ciklusváltozó signed int Összehasonlítás egyszerű Léptetés konstanssal Kiugrás Break, continue, goto join
Közös erőforrások Közös változó Írás Olvasá s Az írás-olvasás között más is beleírhat. Kölcsönös kizárás vagy Csak olvasás
Explicit saját változó Saját változó
Implicit saját változó Saját változó
Közös és saját Egy tömb összes elemét akarjuk összeadni Mi legyen a célváltozó? Közös, mert meg akarjuk tartani Saját, mert többen is írnának bele Ha közös? Kölcsönös kizárás (lassú) Ha saját? Eltűnik amit beleírunk!
Redukció Közös változó Redukció bekapcs. Explicit: saját változó Implicit: sajátok -> közös
Fizika és AP Collide N szál NearCallback Ír a contact-ok listájába -> lock Step N szál máshol keressük a függetlenséget!
Fizikai szimuláció: függetlenség A különböző szigetek függetlenek Számolhatjuk őket külön szálakon Collide: N*N PhysX, ODE Legjobb eset egyforma szigetek Legrosszabb eset N, 1, 1, 1 Ez gyakorlatilag egyszálú
Fizikai szimuláció: függetlenség A különböző szigetek függetlenek Számolhatjuk őket külön szálakon Collide: N*N PhysX, ODE Legjobb eset egyforma szigetek Legrosszabb eset N, 1, 1, 1 Ez gyakorlatilag egyszálú
Funkcionális Legrosszabb eset lassú/gyors eljárások külön szálon pl. beolvasás + helyesírásellenőrzés Adat kevés/sok adat: N, 1, 1, 1 Nem egyforma műveletek Statikus kiosztás Hány szál van? Melyik szál mit csinál?
A megoldás Skálázhatóság Feladat és végrehajtás külön Dinamikus erőforráskiosztás Szálak közötti feladatmozgatás N, 1, 1, 1 -> N/2, 1+N/2, 1, 1 Lassú, gyors -> gyors+lassú/2, lassú/2
Hibrid rendszerek Taszk Funkciórész + valamennyi adat Kicsi: jobb kihasználtság Nagy: kevesebb szinkronizáció Ütemező Taszk-szál összerendelés Egyszálas (gyors legyen) Erőforrás Processzor (szálak) HDD, user (képernyő, billentyűzet)
Fizikai szimuláció: gráfban
Ütemezés Mikor futhat egy taszk? Bemenet (producer-consumer) Erőforrásigények Olvasás/írás? Gyors, egyszerű Critical section-ben fut Amdahl törvénye
Amdahl A varázsképlet : P: párh./soros arány N: CPU-szám Jól gondold meg hol lockolsz!
Ütemezés Taszk list<object*> m_apwritelocks; list<const Object*> m_apreadlocks; bool CanRun(const guard&); CanRun() Erőforrások Bemenetek true list.pop_front() UNLOCK Task.Run() LOCK task=list.front() task.canrun() false list.move(task, vége)
Hibrid rendszerek A végrehajtás és a feladat független Skálázható Legyen elég taszk Logikus: irányított gráfból hozható létre
Párhuzamosítás 1x1 Független feladatok azonosítása Szálak létrehozása Kölcsönös kizárás Olvasás, írás Kommunikáció Közös erőforrások Kommunikáció Producer-consumer
Kölcsönös kizárás Olvasás vs írás Olvasni: akárhányan Írni: csak egy Tanulság: olvasni jó HL2 (Source): 95% Eszközök Mutex, Critical Section, stb. Egyik sem biztonságos De mindegyik lassú
Kommunikáció Nincs kizárás Atomi műveletek Lockless Hardware-es kölcsönös kizárás Kölcsönös kizárás Mutex, CS, stb. Kettős pufferelés Igazi garázsbaillő megoldás
Atomi megoldások Hardverfüggő 1 db aligned read/write mindig atomi ivalue = 0; ++a Beolvasás+növelés+visszaírás = 3 OpenMP: 'atomic' kulcsszó Több atomi művelet?
Több atomi művelet Tipikus hiba idata = 5; bvaliddata = true; Ellenségünk: instruction reordering Az MMU blokkokban írja a memóriát Főleg XBox, de PC-n is előfordul Megoldás Critical section Platformfüggő: barrier utasítások
Lockless Lock Lock, tárolás, unlock Lassú, ismert, biztonságos Lockless CompareAndSwap utasítás Sok lock megoldás erre épül
Lockless Láncolt lista old=null CAS(head, old, new) false -> head=old, újra Tulajdonságok Gyors (hardveres) Végrehajtási idő? Prioritások? ABA-probléma A!= A
A probléma Thread1: A->B->A ABA-probléma Thread2: CAS(&p, A, C)=true Mikor jön elő? Az A érték nem a teljes állapotot tárolja (A!=A) Pl. A = pointer A megoldás 32b: DCAS: 64 bit (32b pointer + azonosító) 64b: aligned pointerek + bitbuherálás
ABA-probléma: láncolt lista A lista struct { Obj* next; } * Eredetileg: A, B Műveletek Thread1: read A (nincs pop) Next = A.next (B) Thread2: pop A, pop B, push A A.next = NULL Thread1: CAS(head, A, C) true, de C.next = B!
Lockless: miért jó? Nincs deadlock (és ez a helyes megoldás?!) Nincs prioritásinverzió Prioritásörökléssel is megoldható Mondjuk itt prioritás sincs Nincs sorbanállás a kamion a leggyorsabb Interrupt handler Malloc, interrupt, a handler-ben malloc? Hatékonyabb, mint a mutex Nagy méretű tömb esetén hogy lockolsz?
Szünet Végig maradt Szünetben hazament
Párhuzamos problémák Instruction reordering A=5; B=1; -> B=1; A=5; CS, barrier Változók regiszterekben Tudatlan fordítók Volatile kulcsszó False sharing Nem hiba, csak lassú
False sharing Struct: A, B elemek Thread1: A-t használja Thread2: B-t használja Helyesen működik Független feladatok Nem kell kölcsönös kizárás Rém lassú miért?
False sharing Thread1: A=1; Cache line piszkos Core2: cache line érvénytelen Thread2: ++B L1 Cache line érvénytelen Újraolvasás: RAM->L2->L1->B Megoldás: Egy cache line = egy core
Kettős pufferelés Memóriaigényes: két példány Késik egy swap -et Nincs lock Kivéve: swap Hatékony Funkcionálisan párhuzamos rendszerek Erősen összekötött rendszerek
Intel Smoke Rendszerek: MI, fizika, grafika, hang, input Az FP a legszebb, de Fizika -> MI, gfx, hang gfx -> MI Input->fizika Change manager Inkább késsen, mint lockoljon Kettős puffer továbbfejlesztve
Intel Smoke FP volt, hibrid lett 1 nagy feladat = sok (soros) taszk Egyszerűbb, mint a teljes párhuzamosítás A rendszerenkénti futási idő nem változik De jobb kitöltés -> párhuzamosíthatóbb
Intel Smoke Observer: Saját másolat a közös objektumokból Nincs lock, amikor olvasol Cserébe késik az információ Change Controller: per frame szinkronizáció 1 frame késés Sok rendszer?
Intel Smoke Fix időnként átviszi a változásokat Clock: game clock (lehet renderfüggetlen)
Lock technikák Hol lockolunk? adathoz v. kódhoz közel Lock helyett guard Private változók = nincs lock! Thread Local Storage Multithreaded Singleton A mutex:adat arány 1:1, N:1, 1:N
Hol lockoljunk? lock Működik? Igen Bővíthető? Nehezen (Enter/Leave) unlock Skálázható? Nehezen (FP)
Hol lockoljunk? nem thread safe Van egy osztály... több szál akarja használni. nem thread safe
Hol lockoljunk? Külső lock Majd megoldja valaki +3 nap debuggolás / osztály (~270eFt) Belső lock Az osztály magának elintézi Esetek 90%-a Hívó által biztosított A hívónak bizonyítania kell, hogy lockolt Ez a +10%
Belső lock guard guard Biztonságos Teljesen thread safe Idiótabiztos Kívülről láthatatlan Bárki használhatja az osztályt Rugalmatlan Annyi lock, ahány művelet.
Hívó által bizonyíték
Hívó által Nem az igazi... Mit bizonyít egy lock_guard<mutex>? Kinek a mutexe? Továbbfejlesztési lehetőségek Típusos lock_guard Lock_guard<Type> Fordítási idejű ellenőrzés Pointeres lock_guard Lock_guard<Type>.m_pOwner Futási idejű ellenőrzés
1:1 1 adat, 1 mutex Lock: minél közelebb Erre van a Get/Set Tömbök?
Modulo N lockolás: pl. páros/páratlan Mitől függ az N? Szálak száma 1:N Egy szál hány elemhez fér hozzá egyszerre
Hívó általi lock 1 lock, N művelet Ez is 1:N! Gyorsabb de egyszálúbb explicit lock implicit unlock
N:1 Több mutex egy adathoz? A szál lefogja az egyiket... B szál a másikat... Sok-sok debuggolás Intel Thread Checker 1000 USD
Tartalom Párhuzamosítás 1x1 Eszközök, technikák FP, AP, kommunikáció Tervezés Az eddigiek használata Top-down/Bottom-up Egy lehetséges jövő Stream programming
Hogyan párhuzamosítsunk? Hol kell párhuzamosítani? Leglassabb részek keresése 90/10-es szabály Független feladatok azonosítása Sugárkövetés Videófeldolgozó program
Megközelítés Top-down rendszerszinttől, lefelé Bottom-up közvetlenül ott, ahol szükséges Call stack módszer Breakpoint a 10%-ba Melyik szinten érdemes próbálkozni?
Példa Független részek Pixelek Leglassabb Sugár-test metszés Top-down Pixelek Bottom-up Testek Sugarak Független részek Pixelek Képkockák Leglassabb Motion blur Top-down Képkockák Bottom-up Pixelek
Előre tervezés Milyen új funkciók lehetnek? Mit kéne tudnia egy év múlva? Milyen új adatok lehetnek? Több? Más? Merre fejlődik a hardver? Több mag SIMD, SIMT
Előre tervezés Általában több adat kerül be mint funkció Videófeldolgozás A frame-ek száma gyorsabban nő, mint a pixeleké Raytracer Szebb képhez több sugár/test ütközés kell Kompromisszum? FP mellett OpenMP-s AP Mindegy mit csinálsz, az OpenMP még belefér
Párhuzamos optimalizáció Ami gyors egy szálon, az nem biztos, hogy párhuzamosítható is. Mátrixszorzás: Triviális: O(n 3 ) Strassen: O(n 2,81 )
Mátrixszorzás Triviális 4 szál Azonos terhelés Strassen 7 szál, aztán 4 Eltérő terhelés Soros rész!
Tartalom Párhuzamosítás 1x1 Eszközök, technikák FP, AP, kommunikáció Tervezés Az eddigiek használata Top-down/Bottom-up Egy lehetséges jövő Stream programming
A jövő Intel core i7 8 mag GPU 8800 GTS = 112 mag (GTX 280: 240) Nem csak grafikára Hogyan programozzuk? HLSL, BrookGPU CUDA / AMD Stream OpenCL C++ (Larrabee)
Stream programming HLSL, Brook Pixel shader, adat=textúra, kimenet=rendertarget 1 helyre írás CUDA/Stream Scatter write Gyártófüggő OpenCL AMD, Apple, nvidia, stb. 2009 Larrabee > 25 db P54C C++ Linux a videókártyán
Stream programming (olvas, feldolgoz, ír)xn Sok, független adat (N>1000) Teljesen azonos feldolgozás (SIMT) Divergencia? Mi hiányzik? Rekurzió Pointerek Össze-vissza írás/olvasás Feltételek/ugrások
CUDA Szálkezelés Buta, gyors (~1000) SIMT Adatkezelés Tömb (textúra) Konstans tömb Háromszintű memória Kommunikáció Barrier ( syncthreads) CPU-s emuláció printf
ThreadID adat index Közös memória Local: blokkon belül Global: grid-en belül Barrier Blokkon belül Kernel float *pin, *pout (stream!) Adat: pin[threadid] CUDA
CUDA példa Thread ID Kernelhívás
Összefoglalás Funkcionális vagy adatpárhuzamosítás FP: ha független funkciók vannak AP: ha sok független adat van N~1000 -> stream Hibrid rendszerek Adatfolyamgráfból -> Taszk + ütemező OpenMP-vel FP/AP együtt Kommunikáció, lock Atomi/Lockless/lock adathoz közel
Elérhetőség Letöltés www.mit.bme.hu/~szell/parhuzamos Kérdések Tódor Balázs, cougar@sch.bme.hu
Köszönöm a figyelmet! A. Alexandrescu: Multithreading and the C++ Type System T. Refenes: How to Start a Multi-Threading RelationShip C. Breshears: 8 Simple Rules for Designing Threaded Applications T. Mattson, L. Meadows: A Hands-on Introduction to OpenMP Intel: Designing the Framework of a Parallel Game Engine T. Leonard (Valve): Dragged Kicking and Screaming: Source Multicore Bruce Dawson (XNA): Multicore Memory Coherence: The Hidden Perils of Sharing Data Cyril Zeller(nVidia): CUDA Tutorial Jon Olick (id): Current Generation Parallelism In Games Ian Buck (nvidia): Programming CUDA Intel: Smoke demo ODE: Open Dynamics Engine Boost C++ libraries Khronos Group: OpenMP, OpenCL Geoff Langdale: Lock-Free Programming Wikipedia: Strassen algorithm