Introduktion till PLINQ

Parallell LINQ (PLINQ) är en parallell implementering av LINQ-mönstret (Language-Integrated Query). PLINQ implementerar den fullständiga uppsättningen LINQ-standardfrågeoperatorer som tilläggsmetoder för System.Linq namnområdet och har ytterligare operatorer för parallella åtgärder. PLINQ kombinerar linq-syntaxens enkelhet och läsbarhet med kraften i parallell programmering.

Dricks

Om du inte är bekant med LINQ har den en enhetlig modell för att köra frågor mot alla uppräkningsbara datakällor på ett typsäkert sätt. LINQ to Objects är namnet på LINQ-frågor som körs mot minnesinterna samlingar, till exempel List<T> och matriser. Den här artikeln förutsätter att du har en grundläggande förståelse för LINQ. Mer information finns i Språkintegrerad fråga (LINQ).

Vad är en parallell fråga?

En PLINQ-fråga liknar på många sätt en icke-parallell LINQ till objekt-fråga. PLINQ-frågor, precis som sekventiella LINQ-frågor, fungerar på valfri minnesintern IEnumerable eller IEnumerable<T> datakälla och har uppskjuten körning, vilket innebär att de inte börjar köras förrän frågan räknas upp. Den främsta skillnaden är att PLINQ försöker utnyttja alla processorer i systemet fullt ut. Det gör den genom att partitionera datakällan i segment och sedan köra frågan på varje segment på separata arbetstrådar parallellt på flera processorer. I många fall innebär parallell körning att frågan körs betydligt snabbare.

Genom parallell körning kan PLINQ uppnå betydande prestandaförbättringar jämfört med äldre kod för vissa typer av frågor, ofta bara genom att lägga till frågeåtgärden AsParallel i datakällan. Parallellitet kan dock introducera sina egna komplexiteter, och inte alla frågeåtgärder körs snabbare i PLINQ. Faktum är att parallellisering faktiskt saktar ned vissa frågor. Därför bör du förstå hur problem som beställning påverkar parallella frågor. Mer information finns i Förstå hastighet i PLINQ.

Kommentar

Den här dokumentationen använder lambda-uttryck för att definiera ombud i PLINQ. Om du inte är bekant med lambda-uttryck i C# eller Visual Basic kan du läsa Lambda-uttryck i PLINQ och TPL.

Resten av den här artikeln ger en översikt över de viktigaste PLINQ-klasserna och beskriver hur du skapar PLINQ-frågor. Varje avsnitt innehåller länkar till mer detaljerad information och kodexempel.

Klassen ParallelEnumerable

Klassen System.Linq.ParallelEnumerable exponerar nästan alla PLINQ-funktioner. Den och resten av namnområdestyperna System.Linq kompileras till System.Core.dll sammansättning. Standardprojekten C# och Visual Basic i Visual Studio refererar båda till sammansättningen och importerar namnområdet.

ParallelEnumerable innehåller implementeringar av alla standardfrågeoperatorer som LINQ till objekt stöder, även om de inte försöker parallellisera var och en. Om du inte är bekant med LINQ kan du läsa Introduktion till LINQ (C#) och Introduktion till LINQ (Visual Basic).

Förutom standardfrågeoperatorerna ParallelEnumerable innehåller klassen en uppsättning metoder som möjliggör beteenden som är specifika för parallell körning. Dessa PLINQ-specifika metoder visas i följande tabell.

ParallelEnumerable-operator beskrivning
AsParallel Startpunkten för PLINQ. Anger att resten av frågan ska parallelliseras, om det är möjligt.
AsSequential Anger att resten av frågan ska köras sekventiellt som en icke-parallell LINQ-fråga.
AsOrdered Anger att PLINQ ska bevara ordningen på källsekvensen för resten av frågan, eller tills ordningen ändras, till exempel med hjälp av en orderby-sats (Order By i Visual Basic).
AsUnordered Anger att PLINQ för resten av frågan inte krävs för att bevara källsekvensens ordning.
WithCancellation Anger att PLINQ regelbundet ska övervaka tillståndet för den angivna annulleringstoken och avbryta körningen om den begärs.
WithDegreeOfParallelism Anger det maximala antalet processorer som PLINQ ska använda för att parallellisera frågan.
WithMergeOptions Ger en ledtråd om hur PLINQ, om det är möjligt, ska sammanfoga parallella resultat tillbaka till bara en sekvens på den förbrukande tråden.
WithExecutionMode Anger om PLINQ ska parallellisera frågan även när standardbeteendet skulle vara att köra den sekventiellt.
ForAll En flertrådad uppräkningsmetod som, till skillnad från iterering över resultatet av frågan, gör att resultaten kan bearbetas parallellt utan att först slås samman till konsumenttråden.
Aggregate Överbelastning En överlagring som är unik för PLINQ och möjliggör mellanliggande aggregering över trådlokala partitioner, plus en slutlig aggregeringsfunktion för att kombinera resultatet av alla partitioner.

Opt-in-modellen

När du skriver en fråga väljer du PLINQ genom att ParallelEnumerable.AsParallel anropa tilläggsmetoden på datakällan, som du ser i följande exempel.

var source = Enumerable.Range(1, 10000);

// Opt in to PLINQ with AsParallel.
var evenNums = from num in source.AsParallel()
               where num % 2 == 0
               select num;
Console.WriteLine("{0} even numbers out of {1} total",
                  evenNums.Count(), source.Count());
// The example displays the following output:
//       5000 even numbers out of 10000 total
Dim source = Enumerable.Range(1, 10000)

' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
               Where num Mod 2 = 0
               Select num
Console.WriteLine("{0} even numbers out of {1} total",
                  evenNums.Count(), source.Count())
' The example displays the following output:
'       5000 even numbers out of 10000 total

Tilläggsmetoden AsParallel binder efterföljande frågeoperatorer, i det här fallet where och select, till implementeringarna System.Linq.ParallelEnumerable .

Körningslägen

PLINQ är som standard konservativt. Vid körning analyserar PLINQ-infrastrukturen frågans övergripande struktur. Om frågan sannolikt ger snabbare hastigheter genom parallellisering partitioner PLINQ källsekvensen i uppgifter som kan köras samtidigt. Om det inte är säkert att parallellisera en fråga kör PLINQ bara frågan sekventiellt. Om PLINQ har ett val mellan en potentiellt dyr parallell algoritm eller en billig sekventiell algoritm väljer den sekventiella algoritmen som standard. Du kan använda WithExecutionMode metoden och System.Linq.ParallelExecutionMode uppräkningen för att instruera PLINQ att välja den parallella algoritmen. Detta är användbart när du genom att testa och mäta att en viss fråga körs snabbare parallellt. Mer information finns i Så här anger du körningsläget i PLINQ.

Grad av parallellitet

Som standard använder PLINQ alla processorer på värddatorn. Du kan instruera PLINQ att använda högst ett angivet antal processorer med hjälp WithDegreeOfParallelism av metoden. Detta är användbart när du vill se till att andra processer som körs på datorn får en viss cpu-tid. Följande kodfragment begränsar frågan till att använda högst två processorer.

var query = from item in source.AsParallel().WithDegreeOfParallelism(2)
            where Compute(item) > 42
            select item;
Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)
            Where Compute(item) > 42
            Select item

Om en fråga utför en betydande mängd icke-beräkningsbundet arbete, till exempel fil-I/O, kan det vara fördelaktigt att ange en grad av parallellitet som är större än antalet kärnor på datorn.

Ordnade kontra osorterade parallella frågor

I vissa frågor måste en frågeoperator generera resultat som bevarar källsekvensens ordning. PLINQ tillhandahåller operatorn AsOrdered för detta ändamål. AsOrdered skiljer sig från AsSequential. En AsOrdered sekvens bearbetas fortfarande parallellt, men resultatet buffrades och sorteras. Eftersom orderbevarande vanligtvis innebär extra arbete kan en AsOrdered sekvens bearbetas långsammare än standardsekvensen AsUnordered . Om en viss ordnad parallell åtgärd är snabbare än en sekventiell version av åtgärden beror på många faktorer.

I följande kodexempel visas hur du väljer att beställa konservering.

var evenNums =
    from num in numbers.AsParallel().AsOrdered()
    where num % 2 == 0
    select num;
Dim evenNums = From num In numbers.AsParallel().AsOrdered()
               Where num Mod 2 = 0
               Select num


Mer information finns i Beställ bevarande i PLINQ.

Parallella kontra sekventiella frågor

Vissa åtgärder kräver att källdata levereras sekventiellt. Frågeoperatorerna ParallelEnumerable återgår automatiskt till sekventiellt läge när det behövs. För användardefinierade frågeoperatorer och användardelegater som kräver sekventiell körning tillhandahåller AsSequential PLINQ metoden. När du använder AsSequentialkörs alla efterföljande operatorer i frågan sekventiellt tills anropas AsParallel igen. Mer information finns i Så här kombinerar du parallella och sekventiella LINQ-frågor.

Alternativ för sammanslagning av frågeresultat

När en PLINQ-fråga körs parallellt måste resultatet från varje arbetstråd sammanfogas tillbaka till huvudtråden för förbrukning av en foreach loop (For Each i Visual Basic) eller infogas i en lista eller matris. I vissa fall kan det vara fördelaktigt att ange en viss typ av sammanslagningsåtgärd, till exempel för att börja producera resultat snabbare. För detta ändamål stöder WithMergeOptions PLINQ -metoden och ParallelMergeOptions uppräkningen. Mer information finns i Kopplingsalternativ i PLINQ.

ForAll-operatorn

I sekventiella LINQ-frågor skjuts körningen upp tills frågan räknas upp antingen i en foreach (For Each i Visual Basic)-loop eller genom att anropa en metod som ToList , ToArray eller ToDictionary. I PLINQ kan du också använda foreach för att köra frågan och iterera genom resultaten. foreach Men i sig körs inte parallellt, och därför kräver det att utdata från alla parallella uppgifter sammanfogas tillbaka till tråden som loopen körs på. I PLINQ kan du använda foreach när du måste bevara den slutliga ordningen på frågeresultaten, och även när du bearbetar resultaten på ett seriellt sätt, till exempel när du anropar Console.WriteLine för varje element. För snabbare frågekörning när orderbevarande inte krävs och när bearbetningen av resultaten kan parallelliseras använder du ForAll metoden för att köra en PLINQ-fråga. ForAll utför inte det här sista sammanslagningssteget. I följande kodexempel visas hur du ForAll använder metoden. System.Collections.Concurrent.ConcurrentBag<T> används här eftersom den är optimerad för flera trådar som läggs till samtidigt utan att försöka ta bort några objekt.

var nums = Enumerable.Range(10, 10000);
var query =
    from num in nums.AsParallel()
    where num % 10 == 0
    select num;

// Process the results as each thread completes
// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll(e => concurrentBag.Add(Compute(e)));
Dim nums = Enumerable.Range(10, 10000)
Dim query = From num In nums.AsParallel()
            Where num Mod 10 = 0
            Select num

' Process the results as each thread completes
' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))

Följande bild visar skillnaden mellan foreach och ForAll när det gäller frågekörning.

ForAll vs. ForEach

Annullering

PLINQ är integrerat med annulleringstyperna i .NET. (Mer information finns i Annullering i hanterade trådar.) Därför kan PLINQ-frågor avbrytas, till skillnad från sekventiell LINQ till objektfrågor. Om du vill skapa en PLINQ-fråga som kan avbrytas använder du operatorn WithCancellation för frågan och anger en CancellationToken instans som argument. IsCancellationRequested När egenskapen på token är inställd på true ser PLINQ det, slutar bearbeta alla trådar och genererar en OperationCanceledException.

Det är möjligt att en PLINQ-fråga kan fortsätta att bearbeta vissa element när annulleringstoken har angetts.

För större svarstider kan du också svara på begäranden om annullering i långvariga användardelegater. Mer information finns i Så här: Avbryt en PLINQ-fråga.

Undantag

När en PLINQ-fråga körs kan flera undantag genereras från olika trådar samtidigt. Dessutom kan koden för att hantera undantaget finnas i en annan tråd än den kod som utlöste undantaget. PLINQ använder AggregateException typen för att kapsla in alla undantag som utlöstes av en fråga och konvertera dessa undantag tillbaka till den anropande tråden. I den anropande tråden krävs bara ett try-catch-block. Du kan dock iterera genom alla undantag som är inkapslade i AggregateException och fånga alla som du kan återställa från på ett säkert sätt. I sällsynta fall kan vissa undantag genereras som inte omsluts i en AggregateException, och ThreadAbortExceptions är inte heller omslutna.

När undantag tillåts bubbla upp igen till kopplingstråden är det möjligt att en fråga kan fortsätta att bearbeta vissa objekt när undantaget har genererats.

Mer information finns i Så här hanterar du undantag i en PLINQ-fråga.

Anpassade partitionerare

I vissa fall kan du förbättra frågeprestanda genom att skriva en anpassad partitionerare som drar nytta av vissa egenskaper hos källdata. I frågan är den anpassade partitioneraren själv det uppräkningsbara objekt som efterfrågas.

int[] arr = new int[9999];
Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(SomeFunction);
Dim arr(10000) As Integer
Dim partitioner As Partitioner(Of Integer) = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))

PLINQ stöder ett fast antal partitioner (även om data kan omtilldelas dynamiskt till dessa partitioner under körningstiden för belastningsutjämning.). For och ForEach stöder endast dynamisk partitionering, vilket innebär att antalet partitioner ändras vid körning. Mer information finns i Anpassade partitionerare för PLINQ och TPL.

Mäta PLINQ-prestanda

I många fall kan en fråga parallelliseras, men omkostnaderna för att konfigurera den parallella frågan överväger den prestandaförmån som uppnås. Om en fråga inte utför mycket beräkning eller om datakällan är liten kan en PLINQ-fråga vara långsammare än en sekventiell LINQ till objekt-fråga. Du kan använda Parallel Prestandaanalys i Visual Studio Team Server för att jämföra prestanda för olika frågor, för att hitta flaskhalsar för bearbetning och för att avgöra om frågan körs parallellt eller sekventiellt. Mer information finns i Concurrency Visualizer and How to: Measure PLINQ Query Performance (Samtidighetsvisualiserare ) och Så här mäter du PLINQ-frågeprestanda.

Se även