Introducere în Parallel LINQ - Metoda AsParallel
În mod implicit, toate elementele colecției în LINQ sunt procesate secvențial, dar începând cu .NET 4.0, în spațiul de nume System.Linq a fost adăugată clasa ParallelEnumerable, care încorporează funcționalitatea PLINQ (Parallel LINQ) și permite efectuarea interogărilor asupra colecției în mod paralel.
În timpul procesării colecției, PLINQ utilizează capacitățile tuturor procesoarelor din sistem. Sursa de date este împărțită în segmente, iar fiecare segment este procesat într-un fir de execuție separat. Acest lucru permite efectuarea interogării pe mașini multicore mult mai rapid.
În același timp, implicit, PLINQ alege procesarea secvențială a datelor. Trecerea la procesarea paralelă are loc în cazul în care aceasta duce la o accelerare a muncii.
Totuși, de obicei, în operațiile paralele cresc costurile suplimentare. Prin urmare, dacă procesarea paralelă necesită potențial resurse mari, PLINQ poate alege procesarea secvențială, dacă aceasta nu necesită resurse mari.
Astfel, sensul utilizării PLINQ este predominant în colecțiile mari sau în operațiile complexe, unde beneficiul paralelizării interogărilor poate compensa costurile suplimentare generate.
De asemenea, trebuie să avem în vedere că, atunci când accesăm un stat partajat în operațiile paralele, se va folosi implicit sincronizarea pentru a evita blocarea reciprocă a accesului la aceste resurse comune. Costurile de sincronizare duc la scăderea performanței, astfel încât este recomandabil să evităm sau să limităm utilizarea resurselor partajate în operațiile paralele.
Metoda AsParallel
Metoda AsParallel() permite paralelizarea interogării asupra sursei de date. Ea este implementată ca metodă de extensie LINQ pentru array-uri și colecții. La apelarea acestei metode, sursa de date este împărțită în părți (dacă este posibil) și asupra fiecărei părți se efectuează operații separat.
Să analizăm un exemplu simplu de găsire a pătratelor numerelor:
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, };
var squares = from n in numbers.AsParallel()
select Square(n);
foreach (var n in squares)
Console.WriteLine(n);
int Square(int n) => n * n;
De fapt, aici avem o interogare LINQ obișnuită, doar că asupra sursei de date se aplică metoda AsParallel.
Rezultatul programului arată că datele au fost selectate pentru găsirea pătratelor nu secvențial. Deci, a avut loc paralelizarea muncii programului:
49
1
9
25
64
4
16
36
Operație similară folosind metode de extensie:
var squares = numbers.AsParallel().Select(x => Square(x));
Metoda ForAll
Codul de mai sus pentru calcularea pătratului unui număr poate fi optimizat și mai mult din punct de vedere al paralelizării. În special, pentru afișarea rezultatului operației paralele se folosește ciclul foreach. Dar utilizarea acestuia duce la creșterea costurilor - este necesar să se unească datele obținute în diferite fire de execuție într-un singur set și apoi să se parcurgă în ciclu. Mai optim în acest caz ar fi utilizarea metodei ForAll(), care afișează datele în același fir de execuție în care sunt procesate:
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, };
// cu ajutorul operatorilor LINQ
(from n in numbers.AsParallel() select Square(n)).ForAll(Console.WriteLine);
// cu ajutorul metodelor de extensie LINQ
numbers.AsParallel().Select(n => Square(n)).ForAll(Console.WriteLine);
int Square(int n) => n * n;
Metoda ForAll() acceptă ca parametru un delegat Action, care specifică acțiunea de executat.