PLINQ = Parallel LINQ – představení


V minulém článku jsem se pokusil představit možnosti, které nabízí Task Parallel Library. Tentokrát bych rád představil související téma – Parallel LINQ, tedy paralelní zpracování LINQ dotazů. Použití PLINQ je dalším krokem (nebo další cestou) k tomu, jak tvořit vícevláknové aplikace.

PLINQ je v mnoha ohledech podobný klasickému LINQ to Objects, ale hlavní rozdíl spočívá v tom, že se ke zpracování dotazu snaží využít všechny procesory počítače – zdroj dat je rozdělen na části, které jsou zpracovávány paralelně v samostatných vláknech a na nich jsou vykonávány požadované dotazy. Toto paralelní zpracování bude mít ve většině případů za následek větší rychlost zpracování dotazu, ale opět je potřeba myslet na to, že v některých případech to může mít za následek snížení rychlosti (například se to může stát v některých scénářích, kde požadujeme seřazení výsledku dotazu podle původního zdroje dat).

Zapojením PLINQ je možné ve většině případů urychlit zpracování stávajících klasických LINQ dotazů (opět je nutné pamatovat na to, že ne vždy se hodí paralelní zpracování) pouze přidáním volání metody AsParallel() na začátku dotazu. Metoda AsParallel() je jednou z metod hlavní třídy PLINQ – třídy ParallelEnumerable.

Třída ParallelEnumerable

Tato statická třída poskytuje extension metody, které ve většině případů představují paralelní ekvivalenty k operátorům LINQ dotazů (sekvenční Where() –> paralelní Where() atd.). Tato třída ale poskytuje také metody, které mají význam pouze pro paralelní zpracování – pouze pro PLINQ.

Jednou z těchto metod je již zmiňovaná metoda AsParallel(), která je volána nad kolekcí dat (IEnumerable, IEnumerable<T> nebo Partitioner) a zajišťuje paralelní zpracování zbytku dotazu. Volání této metody tedy “spouští” PLINQ a dá se mluvit o klíčové metodě PLINQ.

Použití této metody (a použití PLINQ namísto LINQ) je naznačeno v ukázce:

//Klasický sekvenční LINQ:
var peoplesUnder20 = allPeoples.Where(p => p.Age < 20).OrderBy(p => p.Age);
return peoplesUnder20.ToList();
//nebo
var peoplesUnder20 = from p in allPeoples
                        where p.Age < 20
                        orderby p.Age
                        select p;
return peoplesUnder20.ToList();

//Paralelní LINQ - PLINQ:
var peoplesUnder20 = allPeoples.AsParallel().Where(p => p.Age < 20).OrderBy(p => p.Age);
return peoplesUnder20.ToList();
//nebo
var peoplesUnder20 = from p in allPeoples.AsParallel()
                        where p.Age < 20
                        orderby p.Age
                        select p;
return peoplesUnder20.ToList();

Z ukázky je zřetelné, jak použít operátory PLINQ (extension metody ze třídy ParallelEnumerable). Ale protože z ukázky neplynou výhody PLINQ, předvedu je na konkrétních číslech:

  • Oběma částem kódu byla v parametru předána kolekce allPeoples obsahující 10 000 000 objektů typu People a provedení bylo spuštěno 10x a zprůměrováno (zpracování bylo prováděno na počítači se 2 procesorovými jádry):
    • LINQ dotaz – doba zpracování 3942 ms
    • PLINQ dotaz – doba zpracování 2721 ms
    • Je tedy zcela jasné, že použití PLINQ se v tomto případě vyplatí
  • Ovšem v případě, kdy v kolekci allPeoples je pouze 10 000 objektů jsou výsledky takovéto:
    • LINQ dotaz – 3,7 ms
    • PLINQ dotaz – 11,2 ms
    • Rozdíl je z hlediska času minimální, ale tento příklad ukazuje, že ne vždy se vyplácí použití paralelního zpracování!
  • Další zajímavou ukázkou je, že když nebudeme konvertovat výsledek dotazu do listu (metodou ToList()), ale budeme chtít znát počet objektů vrácených dotazem (metoda Count()) opět nad 10 000 000 objekty, výsledky jsou takovéto:
    • LINQ dotaz – 3636 ms
    • PLINQ dotaz – 515 ms
    • V tomto případě je rozdíl dokonce několikanásobný!

Metoda AsParallel() samozřejmě není jedinou metodou, která by byla zajímavá, ale v tomto článku je mým cílem představit PLINQ a ne sepsat dokumentaci nebo někoho PLINQ naučit od A do Z – to je cílem MSDN – PLINQ.

Další možnosti PLINQ

PLINQ umožňuje další možnosti, mezi které patří:

  • Kombinování paralelních a sekvenčních dotazů pomocí metod AsParallel() a AsSequential()
  • Zrušení zpracovávání podobně jako u TPL – pomocí cancelation tokenů a metody WithCancellation()
  • Zajištění stejného seřazení, jako bylo u původního zdroje dat nebo naopak ignorování původního seřazení – metody AsOrdered() a AsUnordered()
  • PLINQ analyzuje dotazy a v případě, kdy uzná za vhodné, že by bylo lepší dotaz zpracovat sekvenčně, zpracuje ho sekvenčně – toto chování se dá potlačit pomocí metody WithExecutionMode()
  • Omezit počet použitých procesorů v případě, že je to potřeba – metoda WithDegreeOfParallelism()
  • atd., opět doporučuji MSDN a sekci věnovanou PLINQ

, , , , , , , , ,

Komentáře jsou uzavřeny.