Uvod
Sa .Net Frameworkom danas možemo razvijati aplikacije koje, bez dodatnog korištenja APIa niskog nivoa, ne iskorištavaju sve resurse multy core procesora. Svaka for ili do petlja izvršava se jednostrano i može iskorištavati resurse samo jednog procesora odnosno jedne niti (Thread). U slučaju kad se određena aplikacija vrti na mašini sa 2 ili 4 core procesora ona će zauzimati oko 50% ili 25% resursa procesora respektivno. U tom slučaju bez dodatnog programiranja i korištenja API-a za višenitno programiranje nismo u stanju da aplikaciju “natjeramo” da koristi sve raspoložive resurse našeg “multy core” PC-a. Ako smo zamišljali da će naše stare aplikacije brže raditi kupovinom novog multy core PC, zasigurno smo došli u zabludu. Jedino što smo postigli jest multitasking i podizanje i rad s više aplikacija istovremeno, međutim aplikacije pojedinačno nisu ubrzane. Danas nove verzije popularnih aplikacija poput AutoCAD 2009, SolidWorks 2008, Adobe PhotoShop i druge podržavaju multy core procesore ali u ograničenim segmentima. Npr. AutoCAD 2009 prilikom renderiranja 3D CAD model pretvara u fotorealistični sliku. Ovo je najzahtjevniji dio aplikacije u kojoj se izvršavaju vrlo obimni proračuni definisanja svakog piksela slike iz 3D scene. Nekada se slike čekale i po nekoliko sati da se “izrenderiraju”. Iako tokom crtanja i modeliranja u AutoACD 2009 nisam primjetio zauzeće procesora u punom kapacitetu osim renderiranja slike. Naredna slika pokazuje zauzeće procesora prilikom renderiranja u AutoCAD 2009.
Primjer zauzeća procesara “Intel Core 2 Quad” prilikom renderiranja
foto realistične slike u AutoCAD 2009
Multy core procesori danas sve više odgovaraju na zahtjeve digitalnog doba: procesuriranja slike, editovanja videa i numeričkih proračuna u raznim poljima tehnike medicine, metorologije, astronomije i td. Samim tim, softverski inžinjeri i ahitekti u svojim rješenjima moraju računati i prilagođavati aplikacije iskorištavanju resursa multy core procesora.
Parallel FX
Kao odgovor na pomenutu tehnologiju Microsoft je na svjetlo dana izbacio nekoliko godina razvijanu tehnologiju Parallel FX – podršku za konkurentno programiranje pod .NET Framework-om. U vrijeme pisanja ovog posta dostupna je CTP verzija Parallel Fx koje je izbačena 29. Novembra 2007. god.
Parallel Fx u cjelokpunom okruženju .net 3.5
Parallel Fx je novo proširenje .NET Frameworka, koje će činiti njegovu buduću verziju. Ovo proširenje doprinosi novom načinu konkurentnog programiranja, a posljedica je hardwerskog razvoja “multy core procesora” koji danas sve više zauzimaju primat u odnosu na dosadašnje tzv. “single core procesore”. Kao što je prikazano na prethodnoj slici Parallel FX čini:
· TPL – Task Paralel Library i
· PLINQ– Parallel LINQ.
Parallel FX predstavlja “managed” upravljani model programiranja paralelnog izvršavanja zadataka, obrade podataka te koordiniranog iskorištanja hardwerskih resursa. Maksimalno iskorištavanje nove generacije hardwera, aplikacije čini visoko učinkovitim sa velikim performansama koje tradicionalne programe poboljašavaju u svim segmentima.
Programirati aplikacije koje iskorištavaju nove mogućnosti hardwera pod .NET Frameworkom mogu se postići na više načina koje obezbjeđuje Parallel FX proširenje i to:
· Deklarativni paralelizam obrade podataka – Parallel LINQ – izvršavanje upita paralelno, maksimalnim iskorištavanjem hardverskih resursa računara. Obzirom da su LINQ upiti deklarativni i da se izvršavaju onda kad se počinje izvršavati prebrojavanje korištenjem foreach ili neke druge klauzule.
· Imperativni paralelizam obrade podataka – čini mehanizam za izvršavanje osnovnih imperativnih podatkovno orjentisanih operacija korištenjem osnovnih petlji programskog jezika for, foreach, do i sl.
· Imperativni paralelizam izvršavanja zadataka – Prethodna dva načina prilagođavaju paralelno programiranje obradi podataka, imperativni paralelizam predstavlja formiranje zadataka koji se mogu izvršavati paralelno i na taj način iskorištavati resurse hardwera.
U ovom postu predstaviti ćemo prva dva načina paralelnog programiranja.
Jedna od najznačajnijih osobina koje odlikuju ovo proširenje je ta da se u vrijeme izvršavanja definiše paralelizam, što ovo proširenje čini maksimalno fleksibilnom i skalabilnom. Ovo pak znači da je proces paralelnog programiranja isti u svim slučajevima broja procesora, a da se u toku izvršavanja “run-time”, formira onoliko niti koliko postoji procesora. U slučaju da se paralelni kod izvršava na single core procesoru, on je u potpunosti kompatibilan i izvršavat će se bez problema. Da li će kod prilagođen paralelnom načinu zvršavanja brže raditi na single core hardveru u odnosu na običan kod? Odgovor je ne, iz razloga dodatnog zauzimanja resursa tokom translacije i pripreme za paralelno izvršavanje.
Task Parallel Library TPL
Osnovu paralelnog razvoja aplikacija u .NET Framework čini TPL biblioteka za paralelno programiranje, u kojoj su implemetirani gornje pobrojani načini paralelizma. Najjednostavniji primjer upotrebe TPL možemo prikazati pomoću for petlje.
Pretpostavimo da imamo for petlju koja izvršava odredjenu operaciju definisanu pomoću metode Funkcija(int a);. Klasični način implementacije možemo prikazati na sljedećem listingu:
Prethodni kod bez obzira koliko iznosio broj n (iteracija u for petlji) koristi resurse samo jednog procesora. To znači kad se aplikacija pokrene na QUAD Core procesoru ona zauzima približno 25% procesora.
Sada se pitamo na koji način navedeni primjer implementirati da podržava paralelizam i iskorištava sve hardwerske resurse multy core procesora. Prethodni primjer prevesti u kod koji podržava paralelizam nimalo nije složen te zahtjeva minimalnu izmjenu koda. TPL biblioteka sadrži standardne metode koje se izvršavaju paralelno. Npr. for petlja u TPL sadrži statičku metodu Parallel.For Parallel klase koja kao argumente uzima početnu i krajnju iteraciju, te delegat koji sadrži implementaciju klasične for petlje.
Npr paralelna verzija prethodnog primjera izgleda kao na sljedećem listingu:
Parallel.For – je statička metoda klase Parallel koja posjeduje 9 preklopljenih verzija koje uzimaju različite argumente za različite načine implementacije paralelizma. O ovoj klasi nešto detaljnije kazat ćemo kasnije. Sada se postavlja logično pitanje. Šta ako imamo dvije ili više for petlji? Da li trebamo paralelizirati vanjsku, unutrašnju ili obe for petlje.
Npr. Pretpostavimo da imamo sljedeću sekvencijalnu implementaciju koda, koja sadrži dvije for petlje. Definišimo da je broj iteracija vanjske i unutrašnje petlje 100.000 iteracija. Postavimo mjerač vremena na početku izvršavanja petlje i na kraju, te izmjerimo vrijeme izvršavanja.
Poslije izvršavanja prethodnog koda rezultat je na sljedećoj slici. Vidimo da ova operacija poprilično dugo traje bez obzira što se radi o QUAD Core procesoru 2.4 GHz. Cijelo vrijeme izvršavanja aplikacije, zauzeće procesora bilo je oko 27%.
Pogledajmo paralelnu verziju i obratimo pažnju na rezultat koji je prikazan na slici.
Iz ovog primjera vidimo da se naš kod u paralelnoj verziji ubrzao oko 4 puta koliko i izosi broj procesra.
Klasa Parallel
Prethodno smo se upoznali sa osnovnim pojmovima Parallel FX odnosno TPL i ovoj sekciji pobliže ćemo obraditi Parallel klasu i različite načine implementacije paralelizma. Pogledamo li iz Object Browsera System.Threading vidjećemo da ovaj dll posjeduje 4 prostora imena, u kojem je smješteno cjelokupno proširenje Parallel FX. Ako proširimo prostor Threading možemo vidjeti klasu Parallel o kojoj ovdje želimo nešto više reći.
Klasa Parallel u ovoj fazi razvoja sadrži samo 3 metode: Do, For i ForEach. Zadnje dvije metode imaju sličnu implementaciju i kao tavkve ćemo ih i posmatrati, dok Do metode ima drgčiju logiku. Prethodnim primjerom smo pokazali kako koristiti statički metodu For, koja je uzimala 3 argumenta, početak i kraj iteracije, te delegat koji implementira anonimnu metodu. Prirast početka i kraja iteracije bio je 1. Međjutim, šta ako u našoj petlji inkrementacija nije 1, nego neki drugi broj npr. 2. U tom slučaju koristime preklopljenu For metodu koja posjeduje dodatni argument Step, koji definiše korak iteracije. Sada bi naš prvi listing izgledao na sljedeći način.
U ovom slučaju broj iteracija iznosi 50.000, obzirom da korak iteracije iznosi 2.
Sljedeći zahtjev koji želimo da potenciramo jeste kako postavljati uslove u paralelnim petljama, i na osnovu vrijednosti uslova prekidati petlju. Sasvim sigurno to ne možemo uraditi pomoću break naredbe, koja inače zaustavlja regularne C# petlje (poput for, foreach, do, while i sl.). Kada bi koristili break naredbu u paralelnoj petlji zaustavili bi samo jednu nit, dok bi ostale radile sve dok bi uslov za prekid pelje u svakoj niti bio ispoštovan. Na kraju teorijski je moguće da se uslov ispuni samo u jednoj niti dok bi se ostale izvršavale onoliko koliko postoji početno definisanih iteracija. U ovakvim uslovima koristimo preklopljenu For metodu koja sadrži argument ParallelState, klasu koja implementira prekid petlji u svim nitima.
Npr. Pretpostavimo da imamo polje brojeva od 0-10, te želimo da pronađemo određeni broj iz polja npr 8. Kada se dotični broj pronađe želimo zaustaviti petlju i prikazati rezultat traženja. Listing takvog primjera nalazi se na sljedećoj slici.
U ovom primjeru smo koristili sljedeću preklopljenu For metodu, koja uzima ParallState klasu kao argument anonimne metode. Ovom klasom kontrolišemo prekid svih iteracija koje se dešavaju paralelno. Ovdje je važno napomenutu da je ParallelState klasa u Parallel FX definisana i kao generička klasa koja ima više namjena. Generički klasu ParallelState<> koristimo kada želimo da dijelimo određenu varijablu kroz paralelne petlje. Npr ako želimo sabrati sjelokupno polje brojeva iz prethodnog primjera koristi ćemo generičku klasu ParallelState<int>.
Sljedeći listing prikazuje primjer korištenja generičke verzije klase kojom izračunava zbir brojeva iz prethodnog primjera.
Na koji način prethodni primjer radi? Obzirom da se For metoda dijeli između niti, sum varijabla se mora u svakoj niti izračunati posebno. Kada se izračunaju sve parcijalne sume u svakoj niti, zadnjom anonimnom metodom objedinjavamo sve parcijalne sume i dobijamo konačnu sumu. Ovaj primjer prikazuje svu jednostavnost TPL-a, koja na elegantna način rješava poprilično složen proces paralelne implementacija sumiranja kroz paralelne petlje. Na skoro identičan način, prethodne implementacije korištenjem metode For mogu se primjeniti na metodu ForEach. Sljedeća metoda koja se nalazi u ovoj klasi je Do. Do metoda radi na različit način od prethodne dvije. Do metodom izvršavamo odredjene zadatke paralelno. Npr. Ako imamo 4 metode koje ne zavise jedna od druge i želimo ih izvršiti paralelno koristi ćemo metodu Do. Npr.
Prethodni primjer sadrži 4 metode koje Do metoda izvršava paralelno. Možemo primjetiti da Do metoda koristi lambda izraze za pozive metoda. Ovom implementacijom sve 4 funkcije izvršavaju se paralelno.
Thread-Safe implementacija
Svaka implementacija paralelnog programiranja za sobom povlači posljedicu da se ne desi slučaj da iz dvije niti pristupa jednom objektu u isto vrijeme, ili popularnije da ne postignemo DeadLock. Korištenjem kolekcija, varijabli i drugih objekata potencijalno postoji opasnosti da dođe do DeadLock. U tom slučaju potrebno je svaki od objekata koji koristimo u paralelnim petljama obezbjediti da bude Thread-Safe.
Buduća verzija Parallel Fx, uključivat će i kolekcije specijalno namijenjene paralelnom programiranju ili Thread Safe Collection, Sihronizacijske i koordinacijske tipove, koji će uveliko pojednostavljivati korištenje Parallel FX. Thread-Safe objekte možemo učiniti na jednostavan način korištenjem ključne riječi lock. Medjutim kada koristimo ovaj način zaključavanja gubimo na performansama aplikacije.
Kolekcije koje koristimo u paralelnim implementacijama potrebno je dodatno osigurati te ili koristiti zaključavanje ili deklarisati ih kao statičke članice. Jedan od klasa koja je takodjer nije Thread-Safe je i Random klasa za generiranja slučajnih brojeva. Naredni primjer pokazuje kako Random klasu učiniti Thread-Safe i sigurno je koristiti u paralelnim implementacijama.
Iz primjera vidimo da smo enkapsulirali klasični klasu Random koristeći lock.
U narednom postu biće riječi o PLINQ –parallel LINQ, paralelnom izvršavanju LINQ upita.
References
1. http://msdn2.microsoft.com/en-us/magazine/cc163340.aspx
2. http://en.wikipedia.org/wiki/Task_Parallel_Library#TPL
3. http://www.danielmoth.com/Blog/
4. Parallel API Reference Help
5. http://msdn2.microsoft.com/en-gb/concurrency
6. http://channel9.msdn.com/showpost.aspx?postid=347531
7. http://spellcoder.com/blogs/bashmohandes/archive/2007/10/14/8530.aspx
8. http://channel9.msdn.com/Showpost.aspx?postid=231495
9. http://channel9.msdn.com/Showpost.aspx?postid=361088
10. http://channel9.msdn.com/Showpost.aspx?postid=361092
11. http://channel9.msdn.com/Showpost.aspx?postid=361091
12. http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=1986&SiteID=1