Kezdtem mostanában már XAML-el álmodni... gyakorlatilag mostanában csak SL2-vel, WPF-el és WF-val dolgoztam...
Úgy gondoltam kicsit visszakanyarodok az alapokhoz : C# és a paralellizmus...
Miről is van szó?
Ma már gyakorlatilag történelem az egy processzort/magot tartalmazó gépek, tehát adott a párhuzamosítás lehetősége architekturális szinten.
És hol tart hozzá a fejlesztő környezet? Runtime? Gyakorlatilag sehol. Pontosabban ugyan ott ahol 10 éve. Van Thread/ThreadPool/Process osztály... no meg BackgroundWorker komponens, de ez végső soron ugyan az...
Vagyis ha akarunk többszálú alkalmazást, akkor írjuk meg mi kézzel! mindent!
De mi nem akarjuk! Nem akarjuk ezeket explicite kézzel írogatni. Nem akarunk adatbázis-, adat-elérést irogatni (fejlődés : DataAdapter/DataSet/LINQ2SQL/EF stb...). Nem akarunk "UX-hez" kapcsolódó dolgokat kézzel kódolni (fejlődés : XAML,WPF,SL2, BLEND,stb...) és így tovább...
Nem akarunk mi kézzel többszálú alkalmazást írogatni. Viszont ki akarjuk használni a több processzor adta lehetőségeket. Implicit. De semmikép nem Explicit.
Tehát akkor hogyan nem?
Itt egy példa : brute force hash gyártás :)))
(természetesen ez csak egy teszt "alogritmus"/állatorvosiLÓ, biztos van jobb,gyorsabb... lehet hibás is... ;-) )
public static void Feltor(object pars)
{
object[] ot = pars as object[];
//mettol
int tol = (int)ot[0];
//meddig kell a vezető karaktert keresni...
int ig = (int)ot[1];
//milyen hosszu a keresendő pass...
int hossz = (int)ot[2];
Console.WriteLine("kereses : Tid={0} - szalon {1}-tol {2}-ig ",System.Threading.Thread.CurrentThread.ManagedThreadId,tol,ig );
string generalt = "";
StringBuilder sb ;
int[] allapot = new int[hossz];
bool vanmeg = true;
MD5 md = MD5.Create();
byte[] jelszo_teszt;
byte[] hash_teszt;
for (int i = tol; i <= ig; i++)
{
//kovetkezo vezeto karakter, az adott szalhoz
allapot[0] = i;
vanmeg = true;
for (int z = 1; z < hossz; z++)
{
allapot[z] = 0;
}
//van meg a vezető karakter utan...
while (vanmeg)
{
if (feltores_sikeres)
{
//ha veletlen megtalaltuk a masik szalon, akkor nem keresunk tovabb...
return;
}
sb = new StringBuilder();
for (int j = 0; j < hossz; j++)
{
//maradek karakterek generalasa az allapothoz....
sb.Append(karakterek.Substring(allapot[j], 1));
}
jelszo_teszt = Encoding.Default.GetBytes(sb.ToString());
hash_teszt = md.ComputeHash(jelszo_teszt);
//szamlalas
Interlocked.Increment(ref ciklus);
if (Program.CompareByteArray(hash_teszt,hash_jelszo))
{
//megvan
feltort_password = sb.ToString();
feltores_sikeres = true;
return;
}
bool atvitel = true;
int k = hossz - 1;
//léptet
while (atvitel)
{
if (allapot[k] == karakterek.Length - 1)
{
atvitel = true;
allapot[k] = 0;
}
else
{
allapot[k] = allapot[k] + 1;
atvitel = false;
}
k--;
if (k == 0 || atvitel == false)
{
break;
}
}
if (atvitel)
{
//nincs több az adott vezető karakterhez...
vanmeg = false;
}
}
}
//pech... nincs meg...
return;
}
Ezt lehet indítani 1-N szálon:
public static void Keres(int hossz,int szalak_szama)
{
Console.WriteLine("Most keresek {1} szálon {0} hosszal!", hossz, szalak_szama);
jelszo_hossz = hossz;
ciklus = 0;
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
Thread[] thr = new Thread[szalak_szama];
int akt = 0;
for (int i = 0; i < szalak_szama; i++)
{
object[] ot = new object[3];
thr[i] = new Thread(new ParameterizedThreadStart(Feltor));
ot[0 ]= akt;
if (i <= szalak_szama -1)
{
ot[1]= akt+ karakterek.Length/szalak_szama-1;
}
else
{
ot[1 ]= karakterek.Length-1;
}
ot[2 ]= jelszo_hossz;
akt += karakterek.Length / szalak_szama;
thr[i].Start(ot);
}
for (int i = 0; i < szalak_szama; i++)
{
thr[i].Join();
}
sw.Stop();
Console.WriteLine("");
Console.WriteLine("eredmény");
Console.WriteLine("{0} ciklus : ", ciklus);
Console.WriteLine("{0} hossz = {1} ms",jelszo_hossz,sw.ElapsedMilliseconds);
Console.WriteLine("Sebesseg {0:f0} db /ms", ciklus / sw.ElapsedMilliseconds);
}
Teszt után egyértelműen kiderül hogy a 2 szál gyorsabb mint az 1 :)
A 4 meg több mint a 2 :)
Az első teszt egy CORE2 DUO 7300-al ment.
A második egy Q6600 proci felett lévő WS2008-Hyper-V-ben lakó Vista-val történt :)
Nos, ez volt ugye az explicit, hagyományos szálkezelés.
Lássuk mit lehet tenni a következőkkel :
Alakítsuk át a kódót a következő módon:
- Adjunk referenciát a System.Threading.dll -re (a Paralell CTP-ben találhatóra)
- Írjuk át a Feltor meghívását, úgy mintha 1 proc. lenne:
Feltor(0, karakterek.Length - 1, hossz);
- Írjuk át a Feltor eljárást:
System.Threading.Parallel.For(tol, ig+1, i =>
{
StringBuilder sb;
int[] allapot = new int[hossz];
bool vanmeg = true;
MD5 md = MD5.Create();
byte[] jelszo_teszt;
byte[] hash_teszt;
...
});
A lényeg hogy bár nem teljesen implicite kezelünk több szálat, mert le kell cserélni a for ciklust a Paralell.For-ra, és mint lambda kifejezés/anonymous-delegate kell átadnunk a ciklus törzsét.
DE sehol nem kell leírni a Thread szót, és még több processzoron kerül végrehajtásra a ciklus:
------------------------
Mi történik a háttérben?
"valamilyen algoritmus" alapján a mögöttes kód(Paralell.For implementációja) eldönti hogy mennyi szálon kellene futtatni (mi lenne az optimális) a ciklust, és hasonlóan ahogy mi felszabdaltuk a ciklust X részre, úgy tesz a Paralell.For is...
Persze fontos hogy a ciklusban végrehajtott action ne akarjon olyan erőforráshoz hozzáférni, amelyhez egy esetleges másik szálon vele párhuzamosan futó ciklustörzs is hozzá akar férni.
Ez lehet egy tényleges másik erőforrás, de lehet pl. olyan eset amikor az n. lépésben az n-1. lépés eredményét fel akarjuk használni. ilyen esetben a párhuzamosítás nem lehetséges. Se hagyományos módon, se a Paralell névtér segítségével se... A párhuzamosítás nagymértékben függ a feladattól, algoritmustól!
-----------------------
A másik része a Paralell Extension-nek a Paralell LINQ.
A LINQ kifejezések legtöbbje párhuzamosítható... kivéve persze ahol az algoritmus ezt nem vagy csak nehezen teszi lehetővé... tipikus példa : rendezés...
Nézzünk rá egy példát:
public static bool IsPrime(int i)
{
...
}
....
int l = 500000;
tomb = new int[l];
for (int i = 0; i < l; i++)
{
tomb[i] = i;
}
Stopwatch sw = new Stopwatch();
sw.Start();
int c = tomb.AsParallel().Where(i => IsPrime(i)).Count();
sw.Stop();
Console.WriteLine("megvan paralell : {0} {1} ms alatt",c,sw.ElapsedMilliseconds);
Console.WriteLine();
sw = new Stopwatch();
sw.Start();
c = tomb.Where(i => IsPrime(i)).Count();
sw.Stop();
Console.WriteLine("megvan siman : {0} {1} ms alatt", c, sw.ElapsedMilliseconds);
Console.ReadLine();
Eredmény:
Ha nem is dupljája de látható hogy egy LINQ lekérdezésen is lehet gyorsítani.
Sőt... kell is a mai 2-4-8 magos PC-ken :)
Rengeteg példa, és maga CTP is letölthető innen:
http://msdn.microsoft.com/en-us/concurrency/default.aspx
Forráskód : http://devportal.hu/groups/fejlesztk/media/p/4256.aspx
Annyi screencast-ot ígértem már mostanában (amelyek hamarosan jönnek), hogy nem merem megígérni, hogy ebből is lesz egy... talán ha CTP-ből valami magasabb fázisba lép a projekt...
--
KRis