Dela via


Snabbhet i PLINQ

Den här artikeln innehåller information som hjälper dig att skriva PLINQ-frågor som är så effektiva som möjligt och som fortfarande ger rätt resultat.

Det primära syftet med PLINQ är att påskynda körningen av LINQ till objekt-frågor genom att köra frågedelegaterna parallellt på datorer med flera kärnor. PLINQ presterar bäst när bearbetningen av varje element i en källsamling är oberoende, utan något delat tillstånd mellan de enskilda ombuden. Sådana åtgärder är vanliga i LINQ till objekt och PLINQ, och kallas ofta "härligt parallella" eftersom de enkelt lämpar sig för schemaläggning på flera trådar. Alla frågor består dock inte helt av härligt parallella åtgärder. I de flesta fall omfattar en fråga vissa operatorer som antingen inte kan parallelliseras eller som saktar ned parallell körning. Och även med frågor som är helt härligt parallella måste PLINQ fortfarande partitioneras datakällan och schemalägga arbetet på trådarna och vanligtvis sammanfoga resultatet när frågan är klar. Alla dessa åtgärder lägger till beräkningskostnaden för parallellisering. dessa kostnader för att lägga till parallellisering kallas omkostnader. För att uppnå optimal prestanda i en PLINQ-fråga är målet att maximera de delar som är härligt parallella och minimera de delar som kräver omkostnader.

Faktorer som påverkar PLINQ-frågeprestanda

I följande avsnitt visas några av de viktigaste faktorerna som påverkar prestanda för parallella frågor. Det här är allmänna instruktioner som i sig inte räcker för att förutsäga frågeprestanda i alla fall. Som alltid är det viktigt att mäta faktiska prestanda för specifika frågor på datorer med en rad representativa konfigurationer och belastningar.

  1. Beräkningskostnad för det övergripande arbetet.

    För att uppnå hastighet måste en PLINQ-fråga ha tillräckligt med härligt parallellt arbete för att kompensera för omkostnaderna. Arbetet kan uttryckas som beräkningskostnaden för varje ombud multiplicerat med antalet element i källsamlingen. Om vi antar att en åtgärd kan parallelliseras, desto dyrare är den, desto större är möjligheten till snabbhet. Om en funktion till exempel tar en millisekunder att köras tar det en sekventiell fråga över 1 000 element en sekund att utföra åtgärden, medan en parallell fråga på en dator med fyra kärnor kanske bara tar 250 millisekunder. Detta ger en hastighet på 750 millisekunder. Om funktionen krävde en sekund för att köra för varje element skulle hastigheten vara 750 sekunder. Om ombudet är mycket dyrt kan PLINQ erbjuda betydande snabbhet med bara några få objekt i källsamlingen. Omvänt är små källsamlingar med triviala delegater i allmänhet inte bra kandidater för PLINQ.

    I följande exempel är queryA förmodligen en bra kandidat för PLINQ, förutsatt att dess Select-funktion innebär mycket arbete. queryB är förmodligen inte en bra kandidat eftersom det inte finns tillräckligt med arbete i Select-instruktionen, och omkostnaderna för parallellisering förskjuts mest eller hela hastigheten.

    Dim queryA = From num In numberList.AsParallel()  
                 Select ExpensiveFunction(num); 'good for PLINQ  
    
    Dim queryB = From num In numberList.AsParallel()  
                 Where num Mod 2 > 0  
                 Select num; 'not as good for PLINQ  
    
    var queryA = from num in numberList.AsParallel()  
                 select ExpensiveFunction(num); //good for PLINQ  
    
    var queryB = from num in numberList.AsParallel()  
                 where num % 2 > 0  
                 select num; //not as good for PLINQ  
    
  2. Antalet logiska kärnor i systemet (grad av parallellitet).

    Den här punkten är en uppenbar följd av föregående avsnitt, frågor som är härligt parallella körs snabbare på datorer med fler kärnor eftersom arbetet kan delas upp mellan fler samtidiga trådar. Den totala mängden hastighet beror på vilken procentandel av frågans övergripande arbete som kan parallelliseras. Anta dock inte att alla frågor körs dubbelt så snabbt på en dator med åtta kärnor som en fyrkärnsdator. När du justerar frågor för optimal prestanda är det viktigt att mäta faktiska resultat på datorer med olika antal kärnor. Den här punkten gäller punkt 1: Större datamängder krävs för att dra nytta av större beräkningsresurser.

  3. Antal och typ av åtgärder.

    PLINQ tillhandahåller AsOrdered-operatorn för situationer där det är nödvändigt att behålla ordningen på elementen i källsekvensen. Det finns en kostnad i samband med beställning, men den här kostnaden är vanligtvis blygsam. GroupBy- och Join-åtgärder medför också omkostnader. PLINQ presterar bäst när det är tillåtet att bearbeta element i källsamlingen i valfri ordning och skicka dem till nästa operator så snart de är klara. Mer information finns i Beställ bevarande i PLINQ.

  4. Form av frågekörning.

    Om du lagrar resultatet av en fråga genom att anropa ToArray eller ToList måste resultaten från alla parallella trådar sammanfogas i den enskilda datastrukturen. Detta innebär en oundviklig beräkningskostnad. På samma sätt måste resultatet från arbetstrådarna serialiseras till uppräkningstråden om du itererar resultaten med hjälp av en foreach-loop (för varje i Visual Basic). Men om du bara vill utföra en åtgärd baserat på resultatet från varje tråd kan du använda metoden ForAll för att utföra det här arbetet på flera trådar.

  5. Typ av kopplingsalternativ.

    PLINQ kan konfigureras för att antingen buffra utdata och producera det i segment eller alla samtidigt efter att hela resultatuppsättningen har skapats, eller för att strömma enskilda resultat när de produceras. Den tidigare resulterar i minskad total körningstid och den senare resulterar i minskad svarstid mellan uttrerade element. Även om sammanslagningsalternativen inte alltid har någon större inverkan på den övergripande frågeprestandan kan de påverka upplevd prestanda eftersom de styr hur länge en användare måste vänta för att se resultat. Mer information finns i Kopplingsalternativ i PLINQ.

  6. Typen av partitionering.

    I vissa fall kan en PLINQ-fråga över en indexerbar källsamling resultera i en obalanserad arbetsbelastning. När detta inträffar kanske du kan öka frågeprestandan genom att skapa en anpassad partitionerare. Mer information finns i Anpassade partitionerare för PLINQ och TPL.

När PLINQ väljer sekventiellt läge

PLINQ försöker alltid köra en fråga minst lika snabbt som frågan skulle köras sekventiellt. Även om PLINQ inte tittar på hur beräkningsmässigt dyra användardelegaterna är, eller hur stor indatakällan är, letar den efter vissa frågeformer. Mer specifikt letar den efter frågeoperatorer eller kombinationer av operatorer som vanligtvis gör att en fråga körs långsammare i parallellt läge. När den hittar sådana former återgår PLINQ som standard till sekventiellt läge.

Men när du har mätt en specifik frågas prestanda kan du fastställa att den faktiskt körs snabbare i parallellt läge. I sådana fall kan du använda ParallelExecutionMode.ForceParallelism flaggan via WithExecutionMode metoden för att instruera PLINQ att parallellisera frågan. Mer information finns i Så här anger du körningsläget i PLINQ.

I följande lista beskrivs de frågeformer som PLINQ som standard körs i sekventiellt läge:

  • Frågor som innehåller en Select-, indexerad Where-, indexerad SelectMany- eller ElementAt-sats efter en ordnings- eller filtreringsoperator som har tagit bort eller ordnat om ursprungliga index.

  • Frågor som innehåller operatorn Take, TakeWhile, Skip, SkipWhile och där index i källsekvensen inte är i den ursprungliga ordningen.

  • Frågor som innehåller Zip eller SequenceEquals, såvida inte någon av datakällorna har ett ursprungligt ordnat index och den andra datakällan är indexerbar (dvs. en matris eller IList(T)).

  • Frågor som innehåller Concat, såvida det inte tillämpas på indexerbara datakällor.

  • Frågor som innehåller Omvänd, om de inte tillämpas på en indexerbar datakälla.

Se även