Delen via


Versnelling in PLINQ

Dit artikel bevat informatie die u helpt bij het schrijven van PLINQ-query's die zo efficiënt mogelijk zijn terwijl er nog steeds juiste resultaten worden geretourneerd.

Het primaire doel van PLINQ is het versnellen van de uitvoering van LINQ naar objectenquery's door de querydelegeringen parallel uit te voeren op computers met meerdere kernen. PLINQ presteert het beste wanneer de verwerking van elk element in een bronverzameling onafhankelijk is, zonder dat er een gedeelde status betrokken is bij de afzonderlijke gemachtigden. Dergelijke bewerkingen zijn gebruikelijk in LINQ naar objecten en PLINQ en worden vaak 'heerlijk parallel' genoemd, omdat ze zich gemakkelijk lenen voor het plannen van meerdere threads. Niet alle query's bestaan echter volledig uit heerlijk parallelle bewerkingen. In de meeste gevallen omvat een query enkele operators die niet kunnen worden geparallelliseerd of die de parallelle uitvoering vertragen. En zelfs met query's die volledig parallel zijn, moet PLINQ nog steeds de gegevensbron partitioneren en het werk op de threads plannen en de resultaten meestal samenvoegen wanneer de query is voltooid. Al deze bewerkingen worden toegevoegd aan de rekenkosten van parallelle uitvoering; deze kosten voor het toevoegen van parallelle uitvoering worden overhead genoemd. Om optimale prestaties te bereiken in een PLINQ-query, is het doel om de onderdelen die heerlijk parallel zijn te maximaliseren en de onderdelen die overhead vereisen, te minimaliseren.

Factoren die van invloed zijn op de prestaties van PLINQ-query's

De volgende secties bevatten enkele van de belangrijkste factoren die van invloed zijn op parallelle queryprestaties. Dit zijn algemene instructies die op zichzelf niet voldoende zijn om queryprestaties in alle gevallen te voorspellen. Zoals altijd is het belangrijk om de werkelijke prestaties van specifieke query's op computers te meten met een reeks representatieve configuraties en belastingen.

  1. Rekenkosten van het totale werk.

    Om sneller te kunnen werken, moet een PLINQ-query voldoende mooi parallel werken hebben om de overhead te compenseren. Het werk kan worden uitgedrukt als de rekenkosten van elke gemachtigde vermenigvuldigd met het aantal elementen in de bronverzameling. Ervan uitgaande dat een bewerking kan worden geparallelliseerd, hoe duurder het is, hoe groter de kans op versnelling. Als een functie bijvoorbeeld één milliseconden nodig heeft om uit te voeren, duurt een sequentiële query van meer dan 1000 elementen één seconde om die bewerking uit te voeren, terwijl een parallelle query op een computer met vier kernen slechts 250 milliseconden kan duren. Dit resulteert in een versnelling van 750 milliseconden. Als de functie één seconde nodig heeft om voor elk element uit te voeren, is de snelheid 750 seconden. Als de gemachtigde erg duur is, kan PLINQ een aanzienlijke versnelling bieden met slechts een paar items in de bronverzameling. Kleine bronverzamelingen met triviale gemachtigden zijn over het algemeen geen goede kandidaten voor PLINQ.

    In het volgende voorbeeld is queryA waarschijnlijk een goede kandidaat voor PLINQ, ervan uitgaande dat de functie Select veel werk omvat. queryB is waarschijnlijk geen goede kandidaat omdat er onvoldoende werk is in de Select-instructie, en de overhead van parallelle uitvoering zal de meeste of alle versnellingen compenseren.

    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. Het aantal logische kernen in het systeem (mate van parallelle uitvoering).

    Dit punt is een duidelijk gevolg van de vorige sectie, query's die heerlijk parallel worden uitgevoerd op machines met meer kernen, omdat het werk kan worden verdeeld over meer gelijktijdige threads. De totale snelheidsverhoging is afhankelijk van het percentage van het totale werk van de query dat parallel kan worden uitgevoerd. Neem echter niet aan dat alle query's twee keer zo snel worden uitgevoerd op een acht kerncomputer als een vier kerncomputer. Bij het afstemmen van query's voor optimale prestaties is het belangrijk om de werkelijke resultaten op computers met verschillende aantallen kernen te meten. Dit punt is gerelateerd aan punt 1: grotere gegevenssets zijn vereist om te profiteren van grotere rekenresources.

  3. Het aantal en het soort bewerkingen.

    PLINQ biedt de operator AsOrdered voor situaties waarin de volgorde van elementen in de bronvolgorde moet worden gehandhaafd. Er zijn kosten verbonden aan bestellen, maar deze kosten zijn meestal bescheiden. GroupBy- en Join-bewerkingen hebben eveneens overhead. PLINQ presteert het beste wanneer het is toegestaan om elementen in de bronverzameling in elke volgorde te verwerken en deze door te geven aan de volgende operator zodra ze klaar zijn. Zie Orderbehoud in PLINQ voor meer informatie.

  4. De vorm van queryuitvoering.

    Als u de resultaten van een query opslaat door ToArray of ToList aan te roepen, moeten de resultaten van alle parallelle threads worden samengevoegd in de afzonderlijke gegevensstructuur. Dit omvat onvermijdelijke rekenkosten. Als u de resultaten herhalen met behulp van een foreach-lus (voor elk in Visual Basic), moeten de resultaten van de werkrolthreads worden geserialiseerd op de enumerator-thread. Maar als u alleen een actie wilt uitvoeren op basis van het resultaat van elke thread, kunt u de Methode ForAll gebruiken om dit werk uit te voeren op meerdere threads.

  5. Het type samenvoegopties.

    PLINQ kan worden geconfigureerd om de uitvoer te bufferen en te produceren in segmenten of allemaal tegelijk nadat de volledige resultatenset is geproduceerd, of om afzonderlijke resultaten te streamen wanneer ze worden geproduceerd. De vroegere resultaten in verminderde totale uitvoeringstijd en de laatste resulteert in een verminderde latentie tussen de geretourneerde elementen. Hoewel de samenvoegopties niet altijd een grote invloed hebben op de algehele queryprestaties, kunnen ze invloed hebben op de waargenomen prestaties, omdat ze bepalen hoe lang een gebruiker moet wachten om resultaten te zien. Zie Opties voor samenvoegen in PLINQ voor meer informatie.

  6. Het soort partitionering.

    In sommige gevallen kan een PLINQ-query voor een indexeerbare bronverzameling leiden tot een niet-verdeelde werkbelasting. Wanneer dit gebeurt, kunt u de queryprestaties mogelijk verhogen door een aangepaste partitioner te maken. Zie Aangepaste partities voor PLINQ en TPL voor meer informatie.

Wanneer PLINQ de sequentiële modus kiest

PLINQ probeert altijd minstens zo snel een query uit te voeren als de query opeenvolgend zou worden uitgevoerd. Hoewel PLINQ niet bekijkt hoe rekenkracht de gedelegeerden van de gebruiker zijn of hoe groot de invoerbron is, zoekt deze wel naar bepaalde query's 'shapes'. Er wordt met name gezocht naar queryoperators of combinaties van operators waardoor een query doorgaans langzamer wordt uitgevoerd in de parallelle modus. Wanneer dergelijke shapes worden gevonden, valt PLINQ standaard terug naar de sequentiële modus.

Na het meten van de prestaties van een specifieke query, kunt u echter bepalen dat deze daadwerkelijk sneller wordt uitgevoerd in de parallelle modus. In dergelijke gevallen kunt u de ParallelExecutionMode.ForceParallelism vlag gebruiken via de WithExecutionMode methode om PLINQ te instrueren om de query te parallelliseren. Zie Procedure: De uitvoeringsmodus opgeven in PLINQ voor meer informatie.

In de volgende lijst worden de queryshapes beschreven die standaard worden uitgevoerd in de sequentiële modus:

  • Query's die een select-, geïndexeerde Where-, geïndexeerde SelectMany- of ElementAt-component bevatten na een rangschikkings- of filteroperator die oorspronkelijke indexen heeft verwijderd of opnieuw heeft gerangschikt.

  • Query's die een Take-, TakeWhile-, Skip-, Skip-, SkipWhile-operator en waar indexen in de bronvolgorde zich niet in de oorspronkelijke volgorde bevinden.

  • Query's die Zip of SequenceEquals bevatten, tenzij een van de gegevensbronnen een oorspronkelijk geordende index heeft en de andere gegevensbron indexeerbaar is (bijvoorbeeld een matrix of IList(T)).

  • Query's die Concat bevatten, tenzij deze worden toegepast op indexeerbare gegevensbronnen.

  • Query's die Omgekeerd bevatten, tenzij deze zijn toegepast op een indexeerbare gegevensbron.

Zie ook