Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Ten artykuł zawiera informacje ułatwiające pisanie zapytań PLINQ, które są tak wydajne, jak to możliwe, przy jednoczesnym zachowaniu prawidłowych wyników.
Głównym celem PLINQ jest przyspieszenie wykonywania zapytań LINQ to Objects przez równoległe wykonywanie delegatów zapytań na komputerach wielordzeniowych. PlINQ działa najlepiej, gdy przetwarzanie każdego elementu w kolekcji źródłowej jest niezależne, bez współużytkowanego stanu między poszczególnymi delegatami. Takie operacje są powszechne w LINQ to Objects i PLINQ i są często nazywane "zachwycająco równoległe", ponieważ są łatwe do planowania na wielu wątkach. Jednak nie wszystkie zapytania składają się całkowicie z wspaniałych operacji równoległych. W większości przypadków zapytanie obejmuje niektóre operatory, które nie mogą być przetwarzane równolegle lub spowalniają równoległe wykonywanie. A nawet w przypadku zapytań, które są całkowicie i przyjemnie równoległe, PLINQ musi nadal partycjonować źródło danych i zaplanować wykonywanie pracy na wątkach, a zwykle scalać wyniki po zakończeniu zapytania. Wszystkie te operacje dodają do kosztów obliczeniowych związanych z równoległością; koszty dodawania równoległości są nazywane dodatkowym narzutem. Aby osiągnąć optymalną wydajność w zapytaniu PLINQ, celem jest zmaksymalizowanie części, które są doskonale równoległe, i zminimalizowanie części, które wymagają narzutu.
Czynniki wpływające na wydajność zapytań PLINQ
W poniższych sekcjach wymieniono niektóre z najważniejszych czynników wpływających na wydajność zapytań równoległych. Są to ogólne instrukcje, które same w sobie nie są wystarczające do przewidywania wydajności zapytań we wszystkich przypadkach. Jak zawsze ważne jest mierzenie rzeczywistej wydajności określonych zapytań na komputerach z szeregiem reprezentatywnych konfiguracji i obciążeń.
Koszt obliczeniowy ogólnej pracy.
Aby osiągnąć szybkość, zapytanie PLINQ musi mieć wystarczającą liczbę wspaniałych równoległych prac, aby zrównoważyć obciążenie. Praca może być wyrażona jako koszt obliczeniowy każdego delegata pomnożony przez liczbę elementów w kolekcji źródłowej. Zakładając, że operacja może być zrównoleglona, im bardziej kosztowna jest, tym większa szansa na przyspieszenie. Jeśli na przykład funkcja przyjmuje jedną milisekundę do wykonania, zapytanie sekwencyjne obejmujące ponad 1000 elementów zajmie jedną sekundę, aby wykonać tę operację, natomiast zapytanie równoległe na komputerze z czterema rdzeniami może potrwać tylko 250 milisekund. Daje to zwiększenie prędkości działania o 750 milisekund. Jeśli funkcja wymagała jednej sekundy do wykonania dla każdego elementu, szybkość będzie 750 sekund. Jeśli delegat jest bardzo kosztowny, to PLINQ może oferować znaczne przyspieszenie tylko z kilkoma elementami w kolekcji źródłowej. Z drugiej strony małe kolekcje źródłowe z trywialnymi delegatami zazwyczaj nie są dobrymi kandydatami do PLINQ.
W poniższym przykładzie zapytanieA jest prawdopodobnie dobrą propozycją dla PLINQ, zakładając, że jego funkcja Select wymaga dużo pracy. Zapytanie B prawdopodobnie nie jest dobrym kandydatem, ponieważ instrukcja Select nie wykonuje wystarczającej ilości operacji, a narzut związany z równoległością zrównoważy większość lub całe przyrost wydajności.
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 PLINQLiczba rdzeni logicznych w systemie (stopień równoległości).
Jest to oczywisty wniosek wynikający z poprzedniej sekcji. Zapytania, które są wyjątkowo równoległe, działają szybciej na maszynach z większą liczbą rdzeni, ponieważ praca może być podzielona między więcej współbieżnych wątków. Ogólna szybkość zależy od tego, jaki procent ogólnej pracy zapytania można zrównoleglić. Nie należy jednak zakładać, że wszystkie zapytania będą działać dwa razy szybciej na ośmiordzeniowym komputerze jako czterordzeniowy komputer. Podczas dostrajania zapytań w celu uzyskania optymalnej wydajności ważne jest mierzenie rzeczywistych wyników na komputerach z różnymi liczbami rdzeni. Ten punkt jest związany z punktem 1: większe zestawy danych są wymagane do korzystania z większych zasobów obliczeniowych.
Liczba i rodzaj operacji.
PLINQ udostępnia operator AsOrdered w sytuacjach, w których konieczne jest zachowanie kolejności elementów w sekwencji źródłowej. Istnieje koszt związany z zamawianiem, ale koszt ten jest zwykle skromny. Operacje GroupBy i Join również powodują narzut. PlINQ działa najlepiej, gdy może przetwarzać elementy w kolekcji źródłowej w dowolnej kolejności i przekazywać je do następnego operatora, gdy tylko będą gotowe. Aby uzyskać więcej informacji, zobacz Zachowywanie kolejności w PLINQ.
Forma wykonywania zapytania.
Jeśli przechowujesz wyniki zapytania przez wywołanie metody ToArray lub ToList, wyniki ze wszystkich równoległych wątków muszą zostać scalone w jedną strukturę danych. Obejmuje to nieunikniony koszt obliczeniowy. Podobnie, jeśli iterujesz wyniki przy użyciu pętli foreach (For Each in Visual Basic), wyniki wątków roboczych muszą być serializowane na wątku wyliczającego. Jeśli jednak chcesz wykonać jakąś akcję na podstawie wyniku z każdego wątku, możesz użyć metody ForAll, aby wykonać tę pracę na wielu wątkach.
Typ opcji scalania.
PLINQ można skonfigurować tak, aby buforować wyniki i produkować je we fragmentach lub wszystkie naraz po utworzeniu całego zestawu wyników, albo przesyłać strumieniowo pojedyncze wyniki w miarę ich tworzenia. Pierwsze z nich prowadzi do zmniejszenia całkowitego czasu wykonywania, a drugie prowadzi do zmniejszenia opóźnienia między zwracanymi elementami. Chociaż opcje scalania nie zawsze mają duży wpływ na ogólną wydajność zapytań, mogą mieć wpływ na postrzeganą wydajność, ponieważ kontrolują, jak długo użytkownik musi czekać, aby zobaczyć wyniki. Aby uzyskać więcej informacji, zobacz Opcje scalania w PLINQ.
Typ partycjonowania.
W niektórych przypadkach zapytanie PLINQ dotyczące kolekcji źródłowej z możliwością indeksowania może spowodować niezrównoważone obciążenie pracą. W takim przypadku może być możliwe zwiększenie wydajności zapytań przez utworzenie niestandardowego partycjonatora. Aby uzyskać więcej informacji, zobacz Custom Partitioners for PLINQ and TPL (Niestandardowe partycjonatory dla PLINQ i TPL).
Gdy plINQ wybiera tryb sekwencyjny
PlINQ zawsze będzie podejmować próby wykonania zapytania co najmniej tak szybko, jak zapytanie będzie uruchamiane sekwencyjnie. Chociaż PLINQ nie patrzy na to, na jakiej obciążenie obliczeniowe narażone są delegaty użytkownika, ani jak duże jest źródło danych wejściowych, to szuka pewnych kształtów zapytań. W szczególności szuka operatorów zapytań lub ich kombinacji, które zwykle powodują spowolnienie zapytania w trybie równoległym. Po znalezieniu takich kształtów funkcja PLINQ domyślnie wraca do trybu sekwencyjnego.
Jednak po zmierzeniu wydajności określonego zapytania można określić, że faktycznie działa szybciej w trybie równoległym. W takich przypadkach można użyć flagi ParallelExecutionMode.ForceParallelism za pośrednictwem WithExecutionMode metody , aby poinstruować PLINQ, aby zrównoleglić zapytanie. Aby uzyskać więcej informacji, zobacz How to: Specify the Execution Mode in PLINQ (Instrukcje: określanie trybu wykonywania w plINQ).
Na poniższej liście opisano kształty zapytań, które PLINQ domyślnie wykonuje w trybie sekwencyjnym.
Zapytania zawierające klauzulę Select, Where z indeksem, SelectMany z indeksem lub ElementAt po operatorze porządkowania lub filtrowania, który usunął lub przemieścił oryginalne indeksy.
Zapytania zawierające operator Take, TakeWhile, Skip, SkipWhile i gdzie indeksy w sekwencji źródłowej nie są w oryginalnej kolejności.
Zapytania zawierające plik Zip lub SequenceEquals, chyba że jedno ze źródeł danych ma pierwotnie uporządkowany indeks, a inne źródło danych można indeksować (tj. tablicę lub IList(T)).
Zapytania zawierające Concat, chyba że są stosowane do źródeł danych, które można indeksować.
Zapytania, które zawierają funkcję "Reverse", chyba że są stosowane do źródła danych, które można indeksować.