Blog Archives
Visual Studio 2010 i C# 4.0 IV DIO: Parallelfx i rješavanje sistema linearnih jednačina
Najznačajnija novina u VS 2010 po mom mišljenju je ParallelFx biblioteka za paralelno programiranje koja iskorištava višejezgrene procesore. O ovoj novini pisao sam na nekoliko postova:
- ParallelFx nova generacija konkurentnog programiranja
- ParallelFx II dio-PLINQ
- Izračunavanje broja PI i implementacija prekoParallelFx
Uvod
Jako puno inženjerskih problema pri procesu rješavanja svodimo na rješavanje sistema linearnih jednačina, koje onda rješavamo jednom od standardnih matematičkih metoda. Rješavanje sistema linearnih jednačina zahtjeva znatno procesorsko vrijeme kao i znatnu količinu memorije za pohranjivanje. Kada rješavamo sisteme sa većim brojem nepoznatih 100, 1000, 10000 i više, vrijeme izvršavanja klasičnim metodama i algoritmima se smanjivalo se kako su se procesori razvijal. Obzirom da se takt procesora više ne može povećavati, a daljnje proširenje svodi se na višejezgrene procesore, naši algoritmi za rješavanje ostali su na istom nivou. Naime, svi ti algoritmi bazirani su na sekvencijalno načinu izvršavanja koda pa višejezgreni procesori nemaju efekta u rješavanju. Pojavom više jezgrenih procesora šire su se počeli razvijati algoritmi za paralelno procesuiranje, a tako i paralelni algoritmi za rješavanje sistema jednačina kao i drugih algoritama koji zahtjevaju značajno procesorsko vrijeme. Paralelni algoritmi za rješavanje određenih inženjerskih procesa pojavili su se početkom 90-tih godina i s njima su se koristile samo velike kompanije poput NASA-e i sličnih, a koji su imali znatne materijalne i financijske resurse. U tu svrhu povezivali su se hiljade procesora koji su onda paralelno izvršavali dijelove koda, a na kraju su se sublimirali i davali rješenja. Tada još nisu bili poznati višejezgreni procesori poput današnjih pa su se procesori povezivali klasično gdje su zauzimali znatno prostora.

Slika 1. 1000-Pentium Beowulf-Style Cluster Computer (www.genetic-programming.org/)
Npr. John Koza tvorac genetskog programiranja, povezao je 1000 procesora gdje je vršio svoja ispitivanja i proračune sa paralelnim algoritmom genetskog programiranja (O genetskom programiranju već sam pisao ). Pojavom višejezgrenih procesora paralelno programiranje postalo je dostupno i običnim „smrtnicima“ a samim tim i šira upotreba paralelnog načina programiranja.
Gaussova metoda eliminacije
Jedna od najpopularnijih metoda rješavanja sistema linearnih jednačina je Gaussova metoda koja koristi princip eliminacije kojom se u konačnom broju iteracija na kraju dobija jedno rješenje, od kojeg se u povratnom smjeru dolazi do drugih rješenja. Linearni sistema od
jednačina sa
nepoznatih
je skup jednačina
slijedeće matrične forme:
…
Gdje su
i
realni brojevi članovi matrice. Sistem zovemo homogenim ako su svi članovi matrice kolone slobodnih članova
jednaki nuli; inače sistema zovemo nehomogeni. Korištenjem matrica i njihovih operacija, jednačine
možemo posmatrati kao matrični jednačinu:
dok su matrice kolona nepoznatih označavamo sa
, a matricu kolona slobodnih članova označavamo sa
. Formalni zapisi ovih matrica dati su sljedećim izrazima respektivno:
Proširena matrica sistema
je matrica reda
koju definišemo kao:
Rješenje sistema linearnih jednačina je skup brojeva
koji zadovoljavaju svih
jednačina, a vektor rješenja sistema linearnih jednačina
je vektor koji zadovoljava matričnu jednačinu. Način na koji se rješavaju sistemi linearnih jednačina Gaussovim sistemom eliminacije možete pogledati iz referenci koje su stavljene na kraju ovog članka. Da bi implementirali algoritma za rješavanje sistema linearnih jednačina potrebno je poznavati ovu metodu, te imati na umu procesorsko vrijeme i memoriju koju će algoritam zauzimati.
Algoritam Gausove metode eliminacije
Način na koji rješavamo sisteme jednačina „pješice“ najbolji je način kako da započnemo implementaciju algoritma. Za informaciju, na internetu postoje hiljade stranica na kojima se mogu naći algoritmi za rješavanje sistema linearnih jednačina u raznim programskim jezicima. S toga ova implementacija koja je data nije ništa revolucionarno.
Algoritam Gauss
– proširena matrica sistema
ULAZ : Proširena matrica sistema koja definiše
članova
, gdje je
, a
- predstavlja broj nepoznati. 1.
Nakon učitavanja matrice sistema potrebno je izvršiti triangulaciju matrice, gdje je po definiciji zadnjih član takve matrice rješenje
. 2. Obrnutim postupkom uz poznavanje rješenja
dolazimo do rješenje
, i tako do rješenja ![]()
IZLAZ : Vektor rješenja
. Izvorni kod ovog algoritma dat je u nastavku:
//Gaussova metoda eliminacije
for (int k = 0; k <= brKol - 2; k++)
{
// Prvo potrazimo najveci koeficijent u kolonama kao Pivot, zbog preciznosti rezultata
double pivot = matSistema[k][k];
for (int i = k + 1; i <= brVrsta - 1; i++)
{
if (pivot < Math.Abs(matSistema[i][k]))
{
double[] temp = matSistema[k];
matSistema[k] = matSistema[i];
matSistema[i] = temp;
}
}
// Triangulacija
for (int j = k + 1; j <= brVrsta - 1; j++)
{
m[j][k] = matSistema[j][k] / matSistema[k][k];
for (int p = k; p <= brKol - 1; p++)
{
matSistema[j][p] = matSistema[j][p] - m[j][k] * matSistema[k][p];
}
}
}
//
double[] rjesenje = new double[brKol - 1];
rjesenje[brKol - 2] = matSistema[brVrsta - 1][brKol - 1] / matSistema[brVrsta - 1][brKol - 2];
for (int i = brVrsta - 2; i >= 0; i--)
{
double sum = 0.0;
for (int j = brKol - 2; j >= i + 1; j--)
{
sum = sum + matSistema[i][j] * rjesenje[j];
}
rjesenje[i] = (1 / matSistema[i][i]) * (matSistema[i][brKol - 1] - sum);
}
return rjesenje;
Kada smo implementirali algoritam za rješavanje sistema linearnih jednačina potrebno je sada analizirati kod i vidjeti gdje bi se mogla izvršiti paralelizacija da bi naš algoritam bio brzi od konvencionalnog odnosno da bi mogao iskorištavati današnja višejezgrene procesore. Prije nego se upustimo u analizu koda potrebno je pretpostaviti da će naš algoritam rješavati više od 500 jednačina odnosno tražiti više od 500 nepoznati. Ako bi algoritam rješavao manje jednačina paralelizacija ne bi imala smisla jer se vrijeme izvršavanja sekvencijalnog koda bilo brže od paralelnog. Ako pogledamo gornju implementaciju mjesta gdje bi paralelizirali kod je samo na dijelovima pri triangulaciji matrice i izračunavanju rješenja. Na ova dva mjesta imamo ugnježdene petlje, a one u algoritmu troše najviše procesorskog vremena.
Kako algoritam počinje…
On uzima prvu kolonu i provjerava da li u danoj koloni prvi član po apsolutnoj vrijednosti je najveći član od svih desnih kolona. Ako to slučaj nije tada se traži kolona u kojoj je prvi član najveći i te dvije kolone zamjenjuju mjesta. To možemo slobodno uraditi zbog osobina matrica. Kada se normalizira kolona tada se vrši triangulacija odnosno za svaku vrstu ispod posmatrane elementi kolone moraju biti nule (Gaussov postupak eliminacije). U ovom koraku se koriste dvije ugnježdene petlje (crveno obojen kod) kojim se ovo ostvaruje. Ovaj dio kod je najpogodniji za paralelnu implementaciju. Razlog zašto se ne ide na paralelnu implementacije prve petlje je taj što se mora ispoštovati poredak kolona, a u paralelnoj implementaciji to ne možemo postići jednostavno. Drugi dio koda pri kojem se na osnovu triangulacije matrice izračunavaju rješenja također koristi dvije ugnježdene petlje, a samim tim i dio koda koji bi mogao biti kandidat za paralelnu implementaciju. Ovaj dio koda također se mora izvršavati u striktno definisanom redu tj. prvo se izračunava zadnja vrsta, pa predzadnja i td. do prve vrste. Kada bi ovaj dio koda bio implementiran paralelno narušili bi poredak, a tako i rješenja ne bi bila izračunata tačno. Na osnovu analize paralelna implementacija kod izgleda na sljedeći način:
//Gaussova metoda eliminacije
for (int k = 0; k <= brKol - 2; k++)
{
// Prvo potrazimo najveci koeficijent u kolonama kao Pivot, zbog preciznosti rezultata
double pivot = matSistema[k][k];
for (int i = k + 1; i <= brVrsta - 1; i++)
{
if (pivot < Math.Abs(matSistema[i][k]))
{ double[] temp = matSistema[k];
matSistema[k] = matSistema[i];
matSistema[i] = temp;
}
}
// Triangulacija
Parallel.For(k + 1, brVrsta, j =>
{
m[j][k] = matSistema[j][k] / matSistema[k][k];
for (int p = k; p <= brKol - 1; p++)
{
matSistema[j][p] = matSistema[j][p] - m[j][k] * matSistema[k][p];
}
}
);
}
//
double[] rjesenje = new double[brKol - 1];
rjesenje[brKol - 2] = matSistema[brVrsta - 1][brKol - 1] / matSistema[brVrsta - 1][brKol - 2];
for (int i = brVrsta - 2; i >= 0; i--)
{
double sum = 0.0;
for (int j = brKol - 2; j >= i + 1; j--)
{
sum = sum + matSistema[i][j] * rjesenje[j];
}
rjesenje[i] = (1 / matSistema[i][i]) * (matSistema[i][brKol - 1] - sum);
}
return rjesenje;
Vidimo da paralelna verzija algoritma se nije puno promijenila a što Parallelfx čini vrlo efikasnom bibliotekom.
Testiranje sekvencijalnog i paralelnog algoritma
Sada kada smo implementirali algoritam potrebno ga je i testirati na višejezgrenim PC mašinama da bi smo se uvjerili da naš paralelni algoritam brže radi od sekvencijalog. U tu svrhu generirano je 5 csv datoteka u kojim stoje proširene matrice sistema od 10, 50, 100, 500 i 1000 nepoznatih, kojim ćemo izvršiti testiranje algoritma. Sistemi su jednostavni čija su rješenja sve jedinice. Test aplikacija je koncipirana tako da učitava sisteme jednačina iz csv datoteka rješava sisteme koristeći sekvencijalni i paralelni algoritam te na konzolu izbacuje vremena koja su potrošena pri rješavanju. Test koji je prezentiran ovdje rađen je na Intel Core 2 Quad CPU Q6600 2,4 GZ:

Rezultat testa je sljedeći:

Iz testa možemo primijetiti da sistemi preko 100 nepoznati daju bolje rezultate kod paralelne implementacije i možemo smatrati okvirno da je to neka granica preko koje bi trebali koristiti paralelnu implementaciju algoritma. Kako broj nepoznatih raste tako raste razlika između paralelne i sekvencijalne implementacije algoritma. Kompletan izvorni kod algoritma i testa može se skinuti sa ovog linka. Projekt je rađen u Visual Studiu 2010 beta 1koji se može skinuti sa ovog linka.
Reference
- http://blogs.msdn.com/pfxteam/
- Ćamila Ljubović, Matematika, IPSvjetlost Sarajevo 1997
- http://geeks.netindonesia.net/blogs/norman
Visual Studio 2010 i C# 4.0 III DIO – Dinamički tipovi podataka
DLR nova komponenta .NET frameworka
U prethodna dva dijela obradili smo neke novine koje dolaze sa novom verzijom C# 4.0, gdje smo se upoznali sa opcionim i imenskim parametrima, kao i ko i kontra varijncama novim svojstvima programskog jezika. U ovom postu govori ćemo o novom svojstvu C# odnosno dinamičkim tipovima podataka u jeziku. Novom verzijom kompletnog .NET 4.0 frameworka microsoft uvodi novu tehnologiju poznatu pod imenom DLR – Dynamic Language Runtime.
DLR implementirana je nad CLR (Common Language Runtime) i nadopunjava je sa skupom tehnologija i alata pomoću kojih se može izvršavati interne operacije u .NET frameworku, te u vrijeme izvršavanja koda pretvarati dinamicke tipove u statičke.
Podrška za DLR u .NET 4.0 sadrži novi prostor imena System.Dynamic. DLR sadrži 3 sloja prikazana na gornjoj slici i to:
- The .NET Language Integration Layer – (Integracijski sloj za .NET jezke)
- The Dynamic Language Runtime Code Components – (Komponente coda DLR)
- Language Binders – (Jezički povezivač)
DLR service sadrži:
- Dynamic Method Dispatch – Prosljeđivač dinamičkih metoda
- Dynamic Code Generation – Generator Dynamičkog koda
- Hosting API – API za hostovanje [preuzeto sa 1]
Statički dinamički tipovi
Novom verzijom C# 4.0 kao i novom proširenju .NET frameworka DLR obezbjeđuje se mogućnost implementacije dinamičkih tipova podataka u C#. Kao što je poznato C# do sada je definisao statički određene tipove podataka. To jednostavno znači da u vrijeme kompajliranja koda poznati su svi tipovi podataka koji se nalaze. Svaka situacija u kojoj kompajler ne može izvršiti konverziju jednog tipa u drugi ili jednostavno deklarisani tip ne poznaje, pojavljivala se greška kompajlera.
Sa dynamic novom ključnom riječju u C#4.0 deklariše se dinamički tip podataka. Ovo znači da ovako deklarisani tip podata je nepoznat u vrijeme kompajliranja, te kompajler neće javiti nikakvu grešku. U vrijeme izvršavanja koda DLR obezbjeđuje da se ovaj dynamički tip deklariše i potpuno definiše u statički. Više informacija o statičkim odnosno dinamičkim tipovima podataka kao i načinu korištenja te tretiranja kompajlera možete naći na stranici wikipedie [2]. Nrp pogledajmo sljedeći dio koda:[primjer preuzet sa 3]
<pre>class Program
{
static void Main(string[] args)
{
foreach (dynamic d in GetValues())
Console.WriteLine("{0} ima {1} godina.", d.Ime, d.Godina);
}
static IEnumerable<dynamic> GetValues()
{
Dictionary<string, int> result = new Dictionary<string, int>();
result["Šemso"] = 30;
result["Elvis"] = 25;
foreach (var item in result)
yield return new { Ime = item.Key, Godina = item.Value };
}
}
Ovakvu konstrukciju koda nismo mogli implementirati u prethodnim verzijama C# je se anonimne klase nisu mogle globalno prenositi, odnosno var ključna riječ mogla se samo lokalno upotrbljavati. Sada kada imamo dynamic ključnu riječ ovakvu konstrukciju odnosno anonimnu klasu možemo imati kao tip podataka koju vraća neka metoda. Na koji nači kompajler tretira ovaj kod. Naime u vrijeme kompajliranja anonimna klasa dobije svoju definicu koja u vrijeme izvršavanja posjeduje statički tip. DLR u vrijeme izvršavanja koda definisani tip koji je kompajler odredio anonimnoj klasu obezbjeđuje vraćanje podataka kroz foreach petlju.
Dinamički tipovi nisu TypeSafe to znači da ako u implementaciji foreach petlje napišemo npr.
<pre>foreach (dynamic d in GetValues())
Console.WriteLine("{0} ima {1} godina.", d.Naziv, d.Godiste);
Kompajler neće pronaći ni upozoriti na bilo kakvu grešku ili upozorenje, a naš program će prilikom pokretanja izbaciti izuzetak da nešto ne štima sa članovima anonimne klase.
Razlika između var i dynamic
Primjetićemo da i u C# 3.0 postoji var ključna riječ koja ima istu funkciju kao dynamic, što je totalno pogrešno. var ključna riječ spoznaje svoj istinski tip podatke u vrijeme kompajliranja. U to se možemo uvjeriti ako na definisani var tip odnesemo miša a intellisence vam već daje informaciju o kojem tipu se podatka radi. To znači da var tip u vrijeme kompajliranja definiše svoj istinski tip podatka, što kod dynamicto nije slučaj.
Reference:
Visual Studio 2010 i C# 4.0 II DIO – Co i Contra Variance
Polja u .NET-u označavana su kao kovarijantna. Isto tako pojavom Generics-a u .NET 2.0 svojstvo kovarijance i kontravarijane nije bilo podržano. U tom pogledu sljedeći kod u C# 3.0 ne može se kompajlirati:
Funkcija1<Student> studemt = () => new Student("Šemso", 1978, DateTime.Now);
Funkcija1<Osoba> osoba = studemt;
Akcija1<Osoba> osoba1 = (s) => { Print(s); };
Akcija1<Student> student1 = osoba1;
Bez obzira što je klasa Student izvedena iz klasa Osoba, ovakva konstrukcija koda nije validna. Upravo Varijanca kao novo proširenje C# 4.0 omogućuje nam konstrukciju koda kao u prethodnom primjeru, i sigurna provjeravanje izvedenih tipova. Jednostavno rečeno Varijanca je svojstvo operatora da se ponašaju kao tipovi. Iz tog razloga definisane su osobine KoVarijanca (Co-Variance) i KontraVarijanca (Contra-Varijance). Da bi prethodni primjer bilo kompajliran bez greške potrebno je Funkciju i Akciju implementirati u smuslu Kovarijantnosti odnosno KontraVarijantnosti.
Tj.
delegate T Funkcija1<out T>(); delegate void Akcija1<in T>(T a);
Deklaracija delegata Funkcija i Akcija imaju sličnu deklaraciju osim ključnih riječi in i out. Ovo znači da prvi delegat Funkcija služi samo za čitanje (read-only) i kao takav je type safe. U slučaju da hoćemo da naš delegat bude write –only odnoso KontraVarijantan tada koristimo ključnu riječ in, kao što je prikazano kod primjera delegata Akcija.
Čitav primjer korištenja Ko i Kontra Varijance dat je i narednom tekstu. Primjer je preuzet sa msdn i modificiran.
class Osoba
{
public Osoba()
{ }
public Osoba(string ime, int id)
{
Ime = ime;
ID = id;
}
public string Ime;
public int ID;
}
class Student : Osoba
{
public Student()
{ }
public Student(string ime, int id, DateTime upis)
{
Ime = ime;
ID = id;
datumUpisa = upis;
}
public DateTime datumUpisa;
}
class Program
{
//Da bi razumijeli nova proširenja CoVarijanca i KontraVarijanca pokušajte
//kompajlirati program kada obrisete ključne riječi out i in sa deklaracije ova dva delegata
delegate T Funkcija1<out T>();
delegate void Akcija1<in T>(T a);
static void Main(string[] args)
{
Funkcija1<Student> student = () => new Student("Šemso", 1978, DateTime.Now);
Funkcija1<Osoba> osoba = student;
Funkcija1<Osoba> osoba2 = ()=> new Osoba("Mirso", 1555);
Akcija1<Osoba> osoba1 = (s) => { Print(s); };
Akcija1<Student> student1 = osoba1;
//Ispis objekta biće student jer je objekat osoba formiran iz studenta
Print(osoba());
//Ispis objekta biće student
student1(new Student("Emir", 1977, DateTime.Now.AddDays(-5)));
//Ispis objekta Osoba
Print(osoba2());
Console.Read();
}
static void Print(Osoba p)
{
Student st = p as Student;
if (st != null)
Console.WriteLine("Student");
else
Console.WriteLine("Osoba;");
Console.WriteLine("Name: {0};", p.Ime);
Console.WriteLine("ID:{0}", p.ID);
if (st != null)
Console.WriteLine("Datum:{0}", st.datumUpisa);
Console.WriteLine("_______________________________");
}
}
Značajno je reći da Varijanca podržavana samo nad delegatima i interfejsima.
Visual Studio 2010 i C# 4.0 I DIO
Uvod

Još se nisu stišali utisci izlaska Visual Studia 2008 i C# 3.0, a Microsoft je još krajem prošle godine najavio svoju novu verziju 2010, kao i novine koje trebaju doći. O nekim novinama već sam pisao. Dio vijesti o VS 2010:
· ParallelFx biblioteka za paralelno programiranje pod .NET jezicima. Ovo proširenje .NET-a je ugledalo svjetlo dana još u decembru 2007 godine lansiranjemm prvog CTP-a ove biblioteke.
· Druga vijest koja je obznanjena da će novi .NET programski jezik F# biti uključen u novi Visual Studio 2010.
· Naravno, pored dobrih vijesti Microsoft je obznanio da napušta razvijanje LINQ to SQL tehnologije, te preporučuje developerima da koriste i prelaze na Entity Framework koji bi u novoj verziji bio “poboljšan“.
U decembru prošle godine na PDC-u, kojeg sam naknadno pregledao najupečatljiviju prezentaciju imao je Andreas Hejlsberg kreator C#. Kao jednu od najboljih prezentacija PDC-a 2008, pored navedene imao je Luca Bolognese sa prezentacijom novog funkcionalnog .NET programskog jezika F#. Preporučujem da je pogledate. Na istoj konferenciji lansirana je i CTP verzija Visual Studia 2010, a instalacija je bila „zalijepljena“ kao virtualna mašina. Naravno zbog samog statusa verzije.
Sredinom maja 2009 izašla je i prva beta verzija Visual Studia 2010, gdje se može skinuti i koristiti u testne i edukacijske svrhe sa ovog linka. Sa prvom betom stekli su se uvjeti da se u punom kapacitetu okušaju sve novine proizvoda.
Pored ParallelFX nove biblioteke odnosno proširenja .NET frameworka, vrlo interesantne stvari dogodile su se u samom C# programskom jeziku koji postaje i ostaje jedan od najpopularnijih programskih jezika. C# 4.0 proširen je u nekoliko smjerova:
1. Imenski i opcioni parametri (Named and Optional parameters)
2. Dinamička podrška
3. Varijance
4. COM interoperabilnost
Opcioni ili podrazumijevani parametri metoda
Developeri koji su prešli sa C++ na C#, ponekad su bili frustrirani nemogućnošću korištenja podrazumijevanih argumenta u funkcijama. Npr:
![]()
Ovo je dovodilo do dodatnih implementacija metoda koje su trebale preklapati isti naziv, a samim tim i do težeg održavanja koda. Konačno, ovo proširenje dolazi sa C# 4.0. Npr. posmatrajmo sljedeći kod koji deklariše jednu metodu koja se može pozvati na tri potpuno različita načina. Zadnji 4-ti poziv metode nije dozvoljen jer se nije ispoštovao princip poretka parametara u metodi.
![]()
Imenski parametri
U prethodnom primjeru vidjeli smo da opcioni parametri ne mogu izgubiti poredak tj. ne smijemo tako lako ispuštati opcioni parametar bez obzira da li on ima podrazumijevanu vrijednost. Puno je slučajeva kada u kojem bi došli u zabludu ako bi zadnji poziv prethodnog primjera bio moguć. Npr. Kada bi metoda Metoda imala podrazumijevane parametre istog tipa ili tipa object vrlo bi teško bilo za samog developera snalaziti se u takvoj zbrci, a da se ne priča o kompajleru.
Da bi se ipak mogao ispustiti podrazumijevani parametar odnosno pozivati metoda na način da se nekoliko podrazumijevanih parametara ispusti, a poslije toga neki drugi poslije njih parametar definiše (kako je prikazano u zanjem pozivu gornjeg primjera) potrebno je koristiti proširenje Imenskih parametara. Naime, ako želimo neke parametre ispustiti, da bi onda ostale dodali potrebno je koristiti sam naziv tog parametra u samoj deklaraciji metode. To znači da prethodni primjer možemo pisati na sljedeći način:
![]()









