Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek obsahuje informace, které vám pomůžou psát dotazy PLINQ, které jsou co nejefektivnější a zároveň poskytují správné výsledky.
Primárním účelem PLINQ je zrychlit provádění dotazů LINQ to Objects spuštěním delegátů dotazů paralelně na počítačích s více jádry. PLINQ funguje nejlépe, když je zpracování každého prvku ve zdrojové kolekci nezávislé a mezi jednotlivými delegáty není žádný sdílený stav. Tyto operace jsou běžné v LINQ to Objects a PLINQ, a často se nazývají "nádherně paralelní", protože se snadno plánují na více vláken. Ne všechny dotazy ale sestávají výhradně z příjemně paralelních operací. Ve většině případů dotaz zahrnuje některé operátory, které nelze paralelizovat nebo které zpomalují paralelní spouštění. A dokonce i s dotazy, které jsou zcela nádherně paralelní, PLINQ musí stále rozdělit zdroj dat a naplánovat práci na vláknech a obvykle sloučit výsledky po dokončení dotazu. Všechny tyto operace se přidávají do výpočetních nákladů paralelizace; tyto náklady na přidání paralelizace se nazývají režijní náklady. Chcete-li dosáhnout optimálního výkonu v dotazu PLINQ, je cílem maximalizovat části, které jsou příjemně paralelní a minimalizovat části, které vyžadují režii.
Faktory ovlivňující výkon dotazů PLINQ
Následující části obsahují některé z nejdůležitějších faktorů, které mají vliv na výkon paralelních dotazů. Jedná se o obecné příkazy, které samy o sobě nestačí k predikci výkonu dotazů ve všech případech. Stejně jako vždy je důležité měřit skutečný výkon konkrétních dotazů na počítačích s řadou reprezentativních konfigurací a zatížení.
Výpočetní náklady na celkovou práci
Aby bylo dosaženo zrychlení, musí mít dotaz PLINQ dostatečně paralelně zpracovatelné úlohy, aby vyvážil režijní náklady. Práce může být vyjádřena jako výpočetní náklady každého delegáta vynásobené počtem prvků ve zdrojové kolekci. Za předpokladu, že operaci lze paralelizovat, tím více výpočetně nákladnější je, tím větší je možnost zrychlení. Pokud například funkce trvá spuštění jednoho milisekundy, bude sekvenční dotaz více než 1 000 prvků trvat jednu sekundu k provedení této operace, zatímco paralelní dotaz na počítači se čtyřmi jádry může trvat pouze 250 milisekund. To představuje zrychlení o 750 milisekund. Pokud funkce vyžadovala jednu sekundu na zpracování každého prvku, časová úspora by činila 750 sekund. Pokud je delegát velmi nákladný, může PLINQ nabídnout výrazné zrychlení již s jen několika málo položkami ve zdrojové kolekci. Naopak malé zdrojové kolekce s triviálními delegáty nejsou obecně vhodnými kandidáty pro PLINQ.
V následujícím příkladu pravděpodobně představuje dotazA dobrého kandidáta pro PLINQ, za předpokladu, že jeho funkce Select vyžaduje hodně práce. dotazB pravděpodobně není vhodným kandidátem, protože ve výběrovém příkazu není dostatek práce a režie paralelizace vykompenzuje většinu nebo všechna zrychlení výkonu.
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 PLINQvar 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 PLINQPočet logických jader v systému (stupeň paralelismu).
Tento bod je zřejmým důsledkem předchozí části. Dotazy, které jsou skvěle paralelní, běží na počítačích s více jádry rychleji, protože práci lze rozdělit mezi více souběžných vláken. Celkové množství zrychlení závisí na tom, jaké procento celkové práce dotazu je paralelizovatelné. Nepředpokládáme však, že všechny dotazy budou běžet dvakrát rychleji na osmi jádrovém počítači jako na čtyřjádrový počítač. Při ladění dotazů pro optimální výkon je důležité měřit skutečné výsledky na počítačích s různými čísly jader. Tento bod souvisí s bodem č. 1: k využití větších výpočetních prostředků se vyžadují větší datové sady.
Počet a druh operací.
PLINQ poskytuje operátor AsOrdered pro situace, kdy je nutné zachovat pořadí prvků ve zdrojové sekvenci. Existují náklady spojené s objednáním, ale tyto náklady jsou obvykle skromné. Operace GroupBy a Join rovněž zahrnují režijní náklady. PLINQ funguje nejlépe, když je povoleno zpracovávat prvky ve zdrojové kolekci v libovolném pořadí a předat je dalšímu operátoru, jakmile jsou připravené. Další informace naleznete v tématu Zachování objednávek v PLINQ.
Forma provádění dotazu.
Pokud ukládáte výsledky dotazu voláním ToArray nebo ToList, výsledky ze všech paralelních vláken musí být sloučeny do jedné datové struktury. To zahrnuje nevyhnutelné výpočetní náklady. Podobně pokud iterujete výsledky pomocí smyčky foreach (For Each v jazyce Visual Basic), výsledky z pracovních vláken musí být serializovány na vlákno enumerátoru. Pokud ale chcete pouze provést nějakou akci na základě výsledku z každého vlákna, můžete k provedení této práce použít ForAll metodu na více vláknech.
Typ možností sloučení.
PLINQ lze nakonfigurovat tak, aby buď ukládal výstup do vyrovnávací paměti a poskytoval ho po částech nebo najednou po vytvoření celé sady výsledků, nebo aby průběžně poskytoval jednotlivé výsledky, jakmile jsou vytvořeny. Výsledkem prvního je snížení celkové doby provádění a výsledkem druhé je snížení latence mezi výnosnými prvky. I když možnosti sloučení nemají vždy velký dopad na celkový výkon dotazů, můžou mít vliv na výkon, protože řídí, jak dlouho musí uživatel čekat na zobrazení výsledků. Další informace naleznete v tématu Možnosti sloučení v PLINQ.
Typ rozdělení.
V některých případech může dotaz PLINQ nad indexovatelnou zdrojovou kolekcí způsobit nevyvážené pracovní zatížení. Pokud k tomu dojde, můžete zvýšit výkon dotazu vytvořením vlastního dělitele. Další informace naleznete v tématu Vlastní děliče pro PLINQ a TPL.
Když PLINQ zvolí sekvenční režim
PLINQ se vždy pokusí spustit dotaz alespoň tak rychle, jak by dotaz běžel postupně. Přestože PLINQ nevyhlíží na výpočetně nákladné delegáty uživatelů nebo jak velký je vstupní zdroj, hledá určité "obrazce dotazu". Konkrétně hledá operátory dotazů nebo kombinace operátorů, které obvykle způsobují pomalejší spouštění dotazu v paralelním režimu. Když takové obrazce najde, plINQ se ve výchozím nastavení vrátí do sekvenčního režimu.
Po měření výkonu konkrétního dotazu ale můžete zjistit, že ve skutečnosti běží rychleji v paralelním režimu. V takových případech můžete příznakem ParallelExecutionMode.ForceParallelism prostřednictvím WithExecutionMode metody instruovat PLINQ k paralelizaci dotazu. Další informace naleznete v tématu Postupy: Určení režimu spouštění v PLINQ.
Následující seznam popisuje obrazce dotazu, které plINQ ve výchozím nastavení spustí v sekvenčním režimu:
Dotazy, které obsahují klauzuli Select, indexed Where, indexed SelectMany nebo ElementAt za operátorem řazení nebo filtrování, který odebral nebo změnil uspořádání původních indexů.
Dotazy obsahující operátor Take, Take While, Skip, Skip While a kde indexy ve zdrojové sekvenci nejsou v původním pořadí.
Dotazy obsahující zip nebo SequenceEquals, pokud některý ze zdrojů dat nemá původně seřazený index a druhý zdroj dat je indexovatelný (tj. pole nebo IList(T)).
Dotazy, které obsahují Concat, pokud nejsou použity pro indexovatelné zdroje dat.
Dotazy, které obsahují Reverse, pokud nejsou použity u indexovatelného zdroje dat.