Sdílet prostřednictvím


Úvod do PLINQ

Co to je paralelní dotaz?

Language-Integrated Query (LINQ) byl zaveden v rozhraní .NET Framework verze 3.0. Obsahuje jednotný model pro dotazování v jakémkoli System.Collections.IEnumerable nebo System.Collections.Generic.IEnumerable<T> zdroji dat typově bezpečným způsobem. LINQ to Objects je název pro LINQ dotazy, které jsou spouštěny proti kolekci v paměť, jako je například List<T> a pole. Tento článek předpokládá, že máte základní znalosti o LINQ. Další informace naleznete v tématu Jazyk LINQ (Language-Integrated Query).

Paralelní LINQ (PLINQ) je implementace LINQ vzoru. PLINQ dotazy se v mnohém podobají neparalelním LINQ to Objects dotazům. PLINQ dotazy, stejně jako sekvenční LINQ dotazy, pracují na všech paměťových IEnumerable nebo IEnumerable<T> zdrojích dat a mají odložené spouštění, což znamená že se nespustí dříve než je vypočten dotaz. Základní rozdíl je, že PLINQ se pokouší plně využívat všechny procesory systému. Toto je realizováno rozdělením zdrojových dat do segmentů a spouštění dotazu na všech segmentech v samostatných pracovních vláknech na více procesorech. V mnoha případech je paralelní spouštění dotazů výrazně rychlejší.

Prostřednictvím paralelního spuštění, PLINQ může dosáhnout, u určitého typu dotazů, lepšího výkonu než v starších verzích kód, často pouze přidáním AsParallel dotazové operace do zdroje dat. Paralelnost však může zavádět vlastní složitosti a ne všechny dotazové operace běží rychleji v PLINQ. Ve skutečnosti paralelizace může zpomalit některé dotazy. Proto je třeba porozumět, jak například třídění ovlivní paralelní dotazy. Další informace naleznete v tématu Principy Vančurovou v PLINQ.

PoznámkaPoznámka

Tato dokumentace používá lambda výrazy k definování delegátů v PLINQ.Pokud nejste obeznámeni s lambda výrazy v jazyce C# nebo Visual Basic, pokračujte na Lambda výrazy v PLINQ a TPL.

Zbytek tohoto článku poskytuje přehled o hlavních třídách PLINQ a popisuje jak vytvořit dotazy v PLINQ. Každý oddíl obsahuje odkazy na podrobnější informace a praktické příklady.

Třída ParallelEnumerable

Třída System.Linq.ParallelEnumerable zpřístupňuje téměř všechny funkce PLINQ. Toto a zbytek oboru názvů System.Linq je kompilovány do sestavení System.Core.dll. Výchozí projekty jazyka C# a Visual Basic v aplikacích se odkazují na toto sestavení a importují obor názvů.

ParallelEnumerable zahrnuje implementaci všech standardních operátorů pro dotazování, které podporují LINQ to Objects, přestože se nepokouší paralelizovat každé z nich. Pokud nejste obeznámeni s LINQ, viz Úvod do LINQ.

Vedle standardních dotazových operátorů, třída ParallelEnumerable obsahuje sadu metod, které umožňují specifické chování pro paralelní spouštění. Tyto metody specifické pro PLINQ jsou uvedeny v následující tabulce.

Operátor ParallelEnumerable

Popis

AsParallel

Vstupní bod pro PLINQ. Určuje, zda ma být zbývající část dotazu paralelizována (pokud je to možné).

AsSequential<TSource>

Určuje, zda zbývající části dotazu má být spuštěna sekvenčně, jako neparalelní LINQ dotaz.

AsOrdered

Určuje, zda by PLINQ mělo zachovat řazení zdrojová sekvence pro zbytek dotazu nebo dokud není řazení změněno, například pomocí klauzule orderby (Order By Vlsual Basic).

AsUnordered<TSource>

Určuje, zda PLINQ má ve zbývající části dotazu vyžadovat zachovávaní řazení zdrojové sekvence dat.

WithCancellation<TSource>

Určuje zda by PLINQ měl pravidelně sledovat stav poskytnutého rušícího tokenu a zrušit spouštění, pokud je o to požádán.

WithDegreeOfParallelism<TSource>

Určuje maximální počet procesorů, které by měl PLINQ používat pro paralelizaci dotazů.

WithMergeOptions<TSource>

Poskytuje informace o způsobu, jakým by měl PLINQ, pokud je to možné, sloučit paralelní výsledek zpět do jediné sekvence v konzumním vlákně.

WithExecutionMode<TSource>

Určuje, zda by měl PLINQ paralelizovat dotaz i v případě, že výchozí nastavení bylo spouštět dotaz sekvenčně.

ForAll<TSource>

Více vláknová enumerační metoda, která na rozdíl od iterování celého výsledku dotazu, umožňuje paralelní zpracování výsledku bez předchozího sloučení zpět do konzumního vlákna.

Aggregatepřetížení

Přetížení, které je jedinečné pro PLINQ, umožňuje přechodnou agregaci přes lokální oddíly vlákna, plus finální agregační funkce k vytvoření výsledku pro všechny oddíly.

Model přidání

Při psaní dotazu zvolíte předání do PLINQ vyvoláním ParallelEnumerable.AsParallel rozšiřující metody na zdrojových datech, jak je znázorněno v následujícím příkladu.

Dim source = Enumerable.Range(1, 10000)

' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
               Where Compute(num) > 0
               Select num
var source = Enumerable.Range(1, 10000);


// Opt-in to PLINQ with AsParallel
var evenNums = from num in source.AsParallel()
               where Compute(num) > 0
               select num;

Přípona metody AsParallel váže následující operátory pro dotazování, v tomto případě where a select, do implementace System.Linq.ParallelEnumerable.

Způsoby spouštění

Ve výchozím nastavení je PLINQ konzervativní. V době spuštění PLINQ infrastruktura analyzuje celkovou strukturu dotazu. Pokud je pravděpodobné, že dotaz bude zrychlen paralelizací ,pak PLINQ rozdělí zdrojovou sekvenci na úlohy, které je možno spustit souběžně. Pokud není bezpečné paralelizovat dotaz, PLINQ spustí dotaz pouze postupně. Pokud se PLINQ rozhoduje mezi potencionálně nákladným paralelním algoritmem nebo levným sekvenčním algoritmem, zvolí ve výchozím nastavení sekvenční algoritmus. Můžete použít WithExecutionMode<TSource> metodu a System.Linq.ParallelExecutionMode výčet k vynucení PLINQ paralelního algoritmu. To je užitečné, když z testování a měření víte, že tento speciální dotaz je vyhodnocen rychleji paralelně. Další informace naleznete v tématu Postupy: Určení režimu spouštění v PLINQ.

Stupeň paralelnost

Ve výchozím nastavení PLINQ používá všechny procesory v hostitelském počítači až po maximum 64. Můžete určit, aby PLINQ používal maximálně určený počet procesorů pomocí metody WithDegreeOfParallelism<TSource>. To je užitečné v případě, že chcete zajistit jinému procesu v tomto počítači určité množství procesorového času. Následující úryvek omezuje dotaz k použití maximálně dvou procesorů.

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

V případech, kde dotaz vykonává značné množství práce bez výpočetní vazby, jako jsou například vstupně-výstupní operace se soubory, může být výhodnější zadat úhel paralelizace větší než je počet jader v počítači.

Uspořádané a neuspořádané paralelní dotazy

V některých dotazech, operátor pro dotazovaní musí předložit výsledky, které zachovávají řazení zdrojová sekvence. Poskytuje PLINQ AsOrdered operátor pro tento účel. AsOrderedse liší od AsSequential<TSource>. AsOrdered Sekvence stále zpracovávány paralelně, ale její výsledky do vyrovnávací paměti a seřazeny. Protože zachování pořadí obvykle zahrnuje dodatečné práce AsOrdered sekvence může zpracovat pomaleji než výchozí AsUnordered<TSource> posloupnosti. Zda je speciální paralelní operace řazení rychlejší než její sekvenční verze závisí na mnoha faktorech.

Následující příklad kódu ukazuje, jak přidat možnosti do zachování pořadí.

        Dim evenNums = From num In numbers.AsParallel().AsOrdered()
                      Where num Mod 2 = 0
                      Select num



            evenNums = from num in numbers.AsParallel().AsOrdered()
                       where num % 2 == 0
                       select num;


Další informace naleznete v tématu Zachování pořadí v PLINQ.

Paralelní vs.sekvenční dotazy

Některé operace vyžadují doručení dat sekvenčním způsobem. Dotazové operátory ParallelEnumerable se vrátí do sekvenčního režimu automaticky pokud je to požadováno. Pro uživatelsky definované operátory pro dotazování a uživatelské delegáty, které vyžadují sekvenční provádění, poskytuje PLINQ AsSequential<TSource> metodu. Při použití AsSequential<TSource>, všechny následující operátory v dotazu jsou prováděny postupně až do znovu zavolání AsParallel. Další informace naleznete v tématu Postupy: Kombinování paralelních a sekvenčních LINQ dotazů.

Možnosti pro slučování výsledků dotazů

Když je PLINQ dotaz spuštěn paralelně, jeho výsledky z každého pracovního vlákno musí být sloučeny zpět do hlavního vlákno pro spotřebu v foreach smyčce (For Each v Visual Basic), nebo pro vložení do seznamu nebo pole. V některých případech může být výhodné například určit konkrétní druh operace sloučení, k zahájení výroby výsledku rychleji. Za tímto účelem podporuje PLINQ WithMergeOptions<TSource> metodou a ParallelMergeOptions výčtu. Další informace naleznete v tématu Možnosti sloučení v PLINQ.

Operátor ForAll

V sekvenčních LINQ dotazy, spuštění odložena do výčtu dotazu buď v foreach ()For Each v Visual Basic) opakovat nebo po vyvolání metody jako ToList<TSource> , ToTSource> , nebo ToDictionary. V PLINQ můžete také použít foreach k provedení dotazu a iterování přes výsledky. Příkaz foreach sám o sobě není spouštěn paralelně a proto je vyžadováno, aby výstupy z paralelní úlohy byl sloučeny zpět do vlákno v němž je spuštěna smyčka. V PLINQ můžete použít příkaz foreach vždy, pokud musíte zachovat konečné řazení výsledku dotazu a také vždy, když zpracováváte výsledky sériovým způsobem, například když voláte Console.WriteLine pro každý element. Pro rychlejší provedení dotazu při zachování pořadí není vyžadováno a použít při zpracování výsledků může sám být parallelized, ForAll<TSource> metody PLINQ dotazu. Metoda ForAll<TSource> neprovádí krok finálního sloučení. Následující příklad kódu ukazuje, jak použít ForAll<TSource> metody. System.Collections.Concurrent.ConcurrentBag<T> je použít, protože je optimalizována pro více podprocesů přidání souběžně bez pokusu o odebrání všech položek.

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)))

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)));

Následující ilustrace ukazuje rozdíl mezi foreach and ForAll<TSource> s ohledem na prováděný dotaz.

Metody ForAll a ForEach

Zrušení

PLINQ je integrována s typy zrušení v .NET Framework 4. Další informace naleznete v tématu Zrušení. Na rozdíl od sekvenční LINQ to Objects dotazů, dotazy PLINQ můžou být zrušeny. Chcete-li vytvořit PLINQ dotaz, který je možné zrušit, použijte operátor WithCancellation<TSource> na dotaz a poskytněte instanci CancellationToken jako argument. Při IsCancellationRequested na tokenu je nastavena na hodnotu true, PLINQ bude si ji zastavit zpracování všech podprocesů a vyvoláním OperationCanceledException.

Je možné, že PLINQ dotaz bude pokračovat ve zpracování některých prvků po nastavení tokenu zrušení.

Pro větší rychlost reakce, můžete také odpovídat na požadavky zrušení v dlouhotrvajících uživatelských delegátech. Další informace naleznete v tématu Postupy: Zrušení PLINQ dotazu.

Výjimky

Při spouštění PLINQ dotazu, může být vyvoláno více výjimek z různých vláken současně. Navíc kód pro zpracování výjimky může být v jiném vlákně, než je kód, který výjimku vyvolal. PLINQ používá AggregateException typ k zapouzdření všech výjimek, jež byly vyvolány v dotazu a zařazení těchto výjimek zpět do volajícího vlákna. Ve volajícím vlákně je požadován pouze jeden blok try-catch. Můžete však iterovat přes všechny výjimky, které jsou zapouzdřeny v AggregateException a zachytit všechny ze kterých se můžete bezpečně zotavit. Ve výjimečných případech některé výjimky může být vyvolána, které nejsou zabaleni v AggregateException, a ThreadAbortExceptions také nejsou zabaleni.

Pokud je výjimkám umožněno probublat zpět do spojovacího vlákna, je možné aby úloha pokračovala ve zpracování některých položek i po tom co nastala vyjímka.

Další informace naleznete v tématu Postupy: Zpracování výjimek v PLINQ dotazu.

Vlastní rozdělovače

V některých případech můžete zlepšit výkon dotazu napsáním vlastního rozdělovače, který využívá některé typické vlastnosti zdrojových dat. V dotazu je vlastní rozdělovač sám vyčíslitelným objektem, který je dotazován.

[Visual Basic]
Dim arr(10000) As Integer
Dim partitioner = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))

[C#]
int[] arr= ...;
Partitioner<int> partitioner = newMyArrayPartitioner<int>(arr);
var q = partitioner.AsParallel().Select(x => SomeFunction(x));

PLINQ podporuje pevný počet oddílů (Ačkoli data mohou být dynamicky přeřazena do těchto oddílů během vyrovnávání zatížení.). For a ForEach podporují pouze dynamické rozdělení, což znamená, že počet oddílů se mění v době běhu. Další informace naleznete v tématu Vlastní Partitioners PLINQ a TPL.

Měření výkon PLINQ

V mnoha případech může být dotaz paralelizován, ale režie nastavení paralelního dotaz převažuje získané výhody výkonu. Jestliže dotaz neprovádí mnoho výpočtu nebo zdrojová data jsou malá, PLINQ dotaz může být pomalejší než sekvenční LINQ to Objects dotazy. Můžete použít paralelního průvodce analýzou výkon v aplikaci Visual Studio Team Server k porovnání výkonu různých dotazů, k nalezení problémových místa v zpracování a zjištění, zda je dotaz prováděn sekvenčně nebo paralelně. Další informace naleznete v tématu Thread Execution Data Views a Postupy: Měření výkonu dotazu PLINQ.

Viz také

Koncepty

Paralelní LINQ (PLINQ)

Principy Vančurovou v PLINQ