You are on page 1of 19

Parallel FX – nova generacija

konkurentnog programiranja
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 koja je izbačena 29. Novembra 2007. god.

Parallel Fx u cjelokpunom okruženju .net 3.511

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.

1 http://en.wikipedia.org/wiki/Task_Parallel_Library#TPL
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. Medjutim, šta ako u našoj petlji inkrementacija nije 1, nego neki drugi broj npr. 2. U tom
slučaju koristimo 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 uslovim 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 nepostignemo DeadLock. Korištenjem
kolekcija, varijabli i drugih objekata potencijalno postoji opasnosti da dođe do DeadLock-a. U tom
slučaju potrebno je svaki objekat koji koristimo u paralelnim petljama obezbjediti da bude Thread-
Safe.

Buduća verzija Parallel Fx, uključivat će i kolekcije specijalno namjenjene 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 impelmentacijama 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.
PLINQ – Parallel LINQ

U prethodnom dijelu upoznali smo se sa TPL – bibliotekom za paralelno programiranje pod .NET
Framework-om. Ovaj post obradit će korištenje PLINQ paralelno izvršavanje LINQ upita. Ukoliko želite
saznati više informacije o LINQ pogledajte prethodne postove ili reference koje su pobrojane u tim
postovima. Postovi se nalaze na sljedećim linkovima: LINQ, LINQ TO SQL I, LINQ TO SQL II.

Paralelno izvršavanje LINQ upita implementirano je na dva od 4 osnovna izvora podataka i to:

 LINQ to Objects, i
 LINQ to XML.

Promjene implementacije upita iz LINQ u PLINQ su minorne i ne zahtjevaju drastično prilagođavanje.
Svako korištenje PLINQ upita nad objektima potrebno je implementirati IparallelEnumerable
interfejs koji čini sastavni dio PLINQ koji je implementiran preko proširenih metoda.

Nabrojimo osnovne značajke PLINQ:

 PLINQ – iskrorištava novu generaciju hardwera korištenjem LINQ upita
 PLINQ podržava sve .NET upite koji se koriste u LINQ,
 Minimalna promjena u implementaciji u odnosu na klasičnu implementaciju LINQ

U prethodnom postu smo vidjeli sastav System.Threading.dll u smislu korištenja TPL. PLINQ je
implementiran u dva osnovna prostora imena i to: System.LINQ i System.LINQ.Parallel.

Na samom početku pokrenimo jedan jednostava primjer izvršavanje LINQ upita i pokušajmo ga
implementirati u paralelnom obliku.

Npr. Pretpostavimo da imamo polje prirodnih brojeva od 1-500.000, i želimo da nad tim poljem da
izvršimo nekoliko LINQ upita. Pretpstavimo da želimoizračunati koliko prostih brojeva se nalazi u
intervalu od 1 do 500.000.

Implementacija sa klasičnim LINQ upitom izgleda kao na sljedcećoj slici:
Rezultat ove implementacije prikazan je na sljedećoj slici:
Vidimo da je traženje prostih brojeva u određenom intervali poprilično zahtjevna operacija i ona iznosi
oko 1 minut.

Kako ovu implementaciju učiniti bržom, ako posjedujemo multy core processor. Ova implemenacija na
QUAD codre procesoru trebala bi u najmanju ruku da bude 4 puta brža, paralelnom implementacijom
LINQ upita. Da bi smo implementirali paralelnu verziju jedino je potrebno da našoj implementaciji
pozovemo samo jednu proširenu metodu i to upit.AsParallel(). Sa ovim pozivom cijela naša
implementacija je prilagođena paralelnom izvršavanju LINQ upita. Sljedeći kod implementira PLINQ:

Rezultat i vrijeme za koje se izvršila operacija prebrojavanja prostih brojeva:
Paralelna verzija se izvršila za 15 sekundi tačno 4 puta brže. Zamislite koliko vremena bi uštedjeli ako
bi smo prebrojavali proste brojeve u intervali od 1 do million ili više.

Zaista, ovo je nevjerojatno na kako jednostava i očigledan način se implementira paralelno izvršavanje
LINQ upita.

PLINQ kao i cjelokupna biblioteka ParallelFX u toku izvršavanje koda formira onoliko niti koliko postoji
procesora na dotičnom PC. U slučaju prethodnog primjera formirane su 4 niti obzirom da se primjer
izvršavao na Quad Core mašini. Medjutim, PLINQ dozvoljava i da se specificira tačan broj niti neovisno
o broju procesora koji sadrži PC. Ovo možemo implementirati preko argumeta int
degreeOfParallelism kojim sepiciramo konkretan broj paralelnih niti. Da bi smo prethodni primjer
izvršili sa 3 paralelne niti implementacija je prikazana na sljedećoj slici:
Kao što smo i čekivali rezultat je 3 puta brzi u odnosu na klasični LINQ upit:
Postavlja se pitanje šta ako za argument stavimo broj veći od broja procesara koji posjeduje PC.
Rezultat će biti približan rezultatu od 15 sekundi. U principu Parallel FX je formirao 16 paralelni niti i
one su se raspodijelile na dostupne procesore. Ovo znači da specificiranje argumenta većeg od broja
procesora nema značenja. Jedina svrha ovog argument je da se paralelizam smanji sa maksimalnog
broja procesora da bi se nekoj drugoj operaciji dala mogućnost paralelnog izvršavanja.

Za kraj ovog posta nužno je spomenuti da zbog paralelizma obrada podataka u listama ili
kolekcijama, ne izvršava se po indeksu i u tom slučaju paralelna obrada podataka nasumice uzima
stavke iz kolekcija i manipuliše snjima. Da bi smo se uvjerili u to u prethodnom primjeru potražimo
proste brojeve od 1 do 20 i ispisimo ih onim redom kojim ih je PLINQ upit i pronašao.

Rezultat je da na sljeećoj slici:

Vidimo da prvi pros broj koji je pronažen je 7, pa zatim 3, 5 2 itd. Ovo znači da sortiranje kolekcija u
paralelnim izvršavanjaima se mora posebno tretirati. Medjutim AsParallel proširena metoda ima drugi
i posljednji argument ParallelQueryOptions, kojim kontrolišemo sortiranje kolekcije. Ako bi đeljeli
da naši prosti brojevi budi pohranjeni u kolekciju uzlaznim redom po veličini tada bi pozvali metodu na
sljedeći način:

Sa ovim smo i pobrojali sve argument koji se mogu pojaviti prilikom poziva AsParallel proširene
metode. Naravno prethodni i zadnji argument mogu se prosljeđivati i u kombinaciji.

Na kraju ako koga zanima implementacija metode pretraživanja prostih brojeva korištene u primjeru
evo listing:
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. 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
13. http://blogs.msdn.com/pfxteam/
14. http://msdn.microsoft.com/en-us/magazine/cc163329.aspx