Monday, January 26, 2009 11:56 AM
JohanLindfors
Slut på gratisätandet
I takt med att hårdvaran den senaste tiden har ökat i prestanda och processerorerna blivit allt snabbare så har vi utvecklare bjudits på en härlig gratisresa. Våra applikationer har helt enkelt ökat i prestanda samtidigt som processorns kapacitet förbättrats, det har kallats att vi bjudits på en ”fri lunch”. Men nu har vi slagit i taket! Processorerna skalar inte längre uppåt utan istället utåt, med fler kärnor istället för mer megahertz. Det innebär också att gratisätandet är slut, vi måste själva ta hänsyn till det ökade antalet parallella instruktioner som processorerna nu kan utföra och det kräver kompetens och nytänkande. Microsoft har sedan ett par år jobbat med just denna utmaning i fokus under ett initiativ som heter Parallel Computing Initiative och nu börjar en del tekniker exporteras från utvecklarna. De första skarpa teknikerna kommer att släppas i och med Visual Studio 2010 lanseringen i både själva utvecklingsmiljön och .NET Framework 4.0. Målet och ambitionen med dessa initiativ är naturligtvis att försöka ge tillbaka gratisätandet och låta även morgondagens applikationer kunna förbättra sin prestanda med tillgång till ökade resurser.
Bilden ovan visar på hur våra tekniker och verktyg grupperas och den ligger lite till grund för den här artikeln. I botten har vi naturligtvis operativsystemet som schemalägger delar av en applikation med hjälp av trådar. Generellt kan sägas att vad operativsystemet försöker att göra är att ge alla applikationer lika mycket tid av processorn genom just schemaläggning, och det är en modell som har fungerat under en längre period, men som i framtiden med stor säkerhet kommer att förändras. Forskning pågår med processorer som är strukturerade i olika delar, där vissa delar är optimerade för enkel-trådade applikationer medans andra delar är optimerade för flertrådade applikationer. Det pågår också utveckling redan i Windows 7 runt olika sätt att få tillgång till resurser, där en modell är att applikationen vid uppstart berättar för operativsystemet och schemaläggaren hur mycket resurser som önskas och får sedan en tilldelning baserat på önskemål och annan last. Kontinuerligt under exekveringen kan sedan operativsystemet justera denna tilldelning vilket gör att applikationer som nyttjar strukturerad parallellism (återkommer till det) kan automatiskt justeras. Men nog om detta, det ligger ett tag framför oss, men är ett oerhört intressant ämne som jag är säker kommer att påverka våra applikationer i framtiden.
Ovanpå operativsystemet ligger ett skikt som kallas “concurrency runtime”, enklast översatt till en exekveringsmotor för parallella applikationer. Ovanpå det skiktet skapar vi en uppsättning programmeringsmodeller, avsedda att underlätta utvecklingen av applikationer som vill ta del av flera kärnor och processorer. Vertikalt i modellen finns naturligtvis de verktyg som underlättar inte bara utvecklingen utan även testning, debuggning och analysering av applikationer med flera trådar, det här är ett område som är oerhört relevant, delvis baserat på avsaknaden av bra verktyg tidigare, men där vi ser fler och fler aktörer bidra med tillägg till exempelvis Visual Studio. Exempelvis Intel åstadkommer med sitt Parallel Studio just en integration med Visual Studio för utveckling och testning. Men låt mig börja från ett annat perspektiv och titta på vilka nyheter som finns för just utveckling av hanterade applikationer.
I exekveringsmiljön skapas bland annat en ny typ av schemaläggare som utgår från en så kallad “work-stealing”-algoritm. Det gör att en vilande tråd kan med hjälp av exekveringsmiljön titta på andra trådar och se om dessa har jobb som väntas på att utföras, och helt enkelt plocka det jobbet från den andra trådens lokala stack och hjälpa till att föra exekveringen framåt. På det sättet åstadkoms en trevlig lastbalansering av systemet, visserligen under inflytande av hur stora uppgifter eller “tasks” (ett nytt begrepp som är tydligare än just en tråd) som försöker att utföras. Det här för mig osökt in på behovet av att partitionera en applikation, något som kräver både god kompetens och en hel del försök och utveckling. I och med att det finns så många faktorer som påverkar en applikations och i det här fallet en “tasks” prestanda, som exempelvis hur mycket minne som måste användas och hur långa exekveringar som krävs. Den resurshanterare som också finns placerad i exekveringsmiljön avser att hantera delar av detta, samt att dela ut systemresurser mellan olika applikationer för att alla ska vara nöjda och glada.
Bland programmeringsmodellerna så erbjuds två delar, det som kallas “Task Parallel Library” eller TPL och PLINQ. TPL innehåller bland annat nya klasser som kan användas för att manuellt skapa “tasks” som utförs på separata trådar. Datatypen Task kommer att finnas både i en generisk och icke generisk typ, där den generisk typen (exempelvis Task<int>) kan användas för att skapa en uppgift som i slutändan avser att returera ett heltalsvärde. Med hjälp av “tasks” kan vi också bygga upp dataflöden, där vi kan säga att när en specifik “task” är färdigexekverad ska den omedelbart dra igång en ny “task”, som kanske till och med får resultatet från den tidigare uppgiften som inparameter och kan fortsätta processering av informationen. TPL innehåller också en mer strukturerad parallellism där vi istället för att explicit skapa egna “tasks” med hjälp av metoder på en statisk klass kallad “Parallel” erbjuda loopar och sätt att dynamisk skapa flera uppgifter. Det här strukturerade sättet att skapa parallellism bäddar gott inför framtidens ytterligare ökningar av prestanda och hårdvara, i och med att ramverket själv kan skapa ytterligare trådar och distribuera lasten.
Ett exempel på ett sådant strukturerat angreppsätt är hur vi kan se till att följande “for”-loop parallelliseras:
for (int i = 0; i < 1000; i++)
{
// Do work
}
Blir helt enkelt:
Parallel.For(0, 1000, i =>
{
// Do work
});
Det finns också Parallel.ForEach och Parallel.Invoke, där den senare helt enkelt tar en uppsättning delegater som inparametrar och utför dessa parallellt.
PLINQ är ett ytterligare initiativ att strutkurerat, ja nästan deklarativ, tala om för exekveringsmotorn att en “LINQ to Objects” eller en “LINQ to XML” fråga ska exekvera parallellt med hjälp av så kallade “extension methods” på de aktuella samlingsklasserna som används i och med LINQ. Det innebär som koden nedan visar att vi kan lägga på .AsParallel() på vårt samlingsobjekt och därigenom låta exekveringsmotorn sprida ut frågan på våra resurser. Teamet har till och med tänkt till på sorteringar och gett oss .AsOrdered() som ytterligare metod vilket alltså också returnerar samlingen sorterad enligt det uttryck som specificerats i frågan.
List<Person> familjen = new List<Person>{
new Person { Name = "Max", Age=2},
new Person { Name = "Matilda", Age=7},
new Person { Name = "Johan", Age = 35},
new Person { Name = "Lina", Age = 36}
};
var barnen = from p in familjen
where p.Age < 18
select p;
Efter tillägget ser frågan ut som följande:
var barnen = from p in familjen.AsParallel()
where p.Age < 18
select p;
Något som också ingår i initiativet för hanterad utveckling är en uppsättning av trådsäkra samlingsklasser som har bättre stöd för att just flera trådar försöker att läsa och skriva mot dess innehåll. Bland annat finns ConcurrentStack<T>, ConcurrentQueue<T> och ConcurrentDictionary<TKey, TValue>.
I min nästa artikel som publiceras inom kort avser jag gå igenom teknikerna för C++-utvecklare och verktygen som underlättar debuggning och analys. Redan idag kan du som utvecklare prova på några av de tekniker som jag berättat om i den här artikeln genom att ladda hem den CTP av TPL som finns tillgänglig. Om jag förstått det rätt så är det den sista publika versionen av TPL som kan användas av .NET Framework 3.5. Den slutliga versionen kommer att vara en del av .NET Framework 4.0 och alltså inte “portas” till tidigare versioner av ramverket. Det går också att prova på en senare version av CTP’n genom att använda den Visual Studio 2010 CTP som släpptes i höstas, men det är inget som jag idag rekommenderar i och med att det kräver en hel del manuellt fixandes att få igång den VPC’n. Där kan du inte heller se prestandaförbättringar (i och med att VPC-images bara använder en kärna) men ändå prova på metoder och klasser.
Om du inte kan vänta på mina kommande artiklar så finns det mängder av information på msdn.microsoft.com/concurrency.