Multicore programmering med Parallel LINQ og Task Parallel Library
Parallel Extensions er et bud på at lave en programmeringsmodel der understøtter multi-core og CPU programmering. Med Parallel extensions kan du lave LINQ forespørgsler mod objekter og XML der kan eksekveres parallelt på flere kerner eller CPU'er. Desuden kommer der med Task Parallel Library flere nye konstruktioner til blandt andet at lave parallelle løkker. CTP'en kan downloades her. I MSDN Magazine er der et par rigtig gode artikler omkring PLINQ og de nye muligheder med Task Parallel Library. Anders Hejlsberg og Joe Duffy snakker her om parallel LINQ på Channel9.
Efter at have installeret installeret Parallel Extensions, får du nogle extension methods tilgængelig når du importerer System.Threading. Først et lille kig på Parallel LINQ.
int degreeOfParallelism = 5;
var listA = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var listB = new int[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
var query = (from x in listA.AsParallel(degreeOfParallelism)
where x.IsMagic()
select x).Union(
from y in listB.AsParallel(degreeOfParallelism)
where y.IsMagic()
select y);
TimeSpan ts = new TimeSpan(DateTime.Now.Ticks);
Console.WriteLine(query.Count());//det er her eksekveringen sker og tiden bruges
ts = ts.Subtract(new TimeSpan(DateTime.Now.Ticks)).Negate();
Console.WriteLine(ts.TotalMilliseconds);
Det interessante i ovenstående er, at jeg i min forespørgsel nu kan specificere .AsParallel() på mine lister – hvilket er den eneste tilføjelse i forhold til den "gamle" LINQ syntaks. I stedet for at få en IEnumerable<int> tilbage fra forespørgselen får man nu en IParallelEnumerable<int>. Når jeg efterfølgende beder om resultater fra min query, med f.eks. et kald til Count, så er det den parallelle implementation af Count der bliver kaldt.
For at sikre at forespørgslen tager lidt tid har jeg lavet en extension method, IsMagic(), på int, der skal simulere en kompleks operation. For at kunne benchmarke resultaterne er det rart/nødvendigt at svartiderne er konstante.
public static bool IsMagic(this int i)
{
System.Threading.Thread.Sleep(500);
return true;
}
Uden .AsParallel eller med .AsParallel(1) tager ovenstående omtrent 10 sekunder (2*10*500/1000), med degreeOfParallelism = 5 kan det eksekveres på ca 3,5 sekunder. Hvis jeg sætter en til 2, så køres forespørgselen på godt 5 sekunder. Hvis man sætter degreeOfParallelism til mere end 5 stiger eksekveringstiden. Min proces for at finde det (formentlig) bedste degreeOfParallelism har været lidt brute force! Det bliver mere interessant når input varierer og det måske ikke er optimalt at degreeOfParallelism er konstant. Umiddelbart tror jeg ikke der er nogen simpel løsning på den problemstilling.
Med Task Parallel Library (TPL) får man yderligere nogle muligheder for at parallelisere. Der er kommet en Parallel klasse, der implementerer nogle statiske metoder, som giver en syntaks for parallelle løkker, der smager lidt af de løkker vi allerede kender. Døm selv:
Parallel.For(0, 10,
i =>{ //kunne også skrives som: delegate(int i){
(listA[i]+=100).IsMagic(); //en kryptisk måde at lægge 100 til og vente i 500 ms
});
Ovenstående paralleliseres, og eksekveres 10 gange. Dette giver en eksekveringstid på ovenstående er mindre en 5 sekunder som den ville være hvis loopet blev eksekveret sekventielt. På samme vis er der en Parallel.ForEach:
Parallel.ForEach(listA, (e,i)=>
{
(listA[i] = e+ 100).IsMagic();
}
);
Ud over selve elementet (e), så tager den også en tæller med (i) så man ved hvilket element i listen der arbejdes på. Rigtig interessant bliver det når man begynder at neste disse nye parallel konstruktioner. Jeg kan klart anbefale at bruge lidt clock-cycles på at læse Optimize Managed Code For Multi-Core Machines på MSDN.
Der er mange gode håndtag at trække i med PLINQ og TPL. Det lader dog til, at det stadig er et mindre skridt på vej mod bedre løsning. Under TechFesten sad jeg ved siden af Eric Rudder (rådgiver til Bill Gates) under middagen. Han havde lige holdt en meget spændende keynote om hvordan han/Microsoft ser fremtiden. Et af emnerne i hans keynote var parallel computing. Under keynoten sagde Eric at Microsoft kigger på deklarative måder at håndtere parallelisering- pfx og PLINQ er imperative. Der var ingen hints til hvad det eventuelt måtte betyde.