Krótkie wprowadzenie
System.Collections.Generic.List <T>to klasa zbiorów ogólnych w .NET, która może przechowywać dowolny typ danych dzięki wygodzie i bogatemu API, szeroko używanej w naszym codziennym życiu i można ją uznać za najczęściej używaną klasę kolekcji.
W pisaniu kodu często musimy iterować <T>przez kolekcję List, aby uzyskać elementy z niej do niektórych procesów biznesowych. Zazwyczaj w zestawie nie ma wielu elementów i bardzo szybko się po nim porusza. Ale w przypadku przetwarzania big data, statystyki, obliczeń w czasie rzeczywistym itd<T>., jak szybko przeszukać kolekcję dziesiątek lub setek tysięcy danych z list? To właśnie muszę dziś z wami powiedzieć.
Tryb przejścia
Przyjrzyjmy się wydajności różnych metod przechodzenia i zbudujmy następujący benchmark wydajności, wykorzystując różne rządy wielkości przejścia kolekcji, aby sprawdzić wydajność różnych metod. Fragment kodu wygląda tak:
Użyj instrukcji foreach
Foreach jest najczęstszym sposobem przeglądania kolekcji, jest to implementacja wzorca iteratora w formacji cukru składniowego i służy także jako punkt odniesienia dla tego czasu.
Ponieważ instrukcja foreach jest składnią cukru, kompilator ostatecznie wywołuje GetEnumerator() i MoveNext() za pomocą pętli while, aby zaimplementować tę funkcjonalność. Skompilowany kod wygląda tak:
Implementacja metody MoveNext() zapewni, że nie będzie innych wątków modyfikujących kolekcję w iteracji, a jeśli modyfikacja nastąpi, wygeneruje wyjątek InvalidOperationException oraz sprawdzi overflow check, aby sprawdzić, czy aktualny indeks jest legalny, a także musi przypisać odpowiadający element do popisowanego. Obecna cecha,W rzeczywistości jego wydajność nie jest najlepsza, fragment kodu wygląda tak:
Przyjrzyjmy się, jak działa na różnych rozmiarach zestawów, a wyniki wyglądają tak:
Widać, że w przypadku różnych rozmiarów wymagana jest liniowa zależność wzrostu czasochłonnego procesu, nawet jeśli przechodzi on 100W danych bez logiki przetwarzania, zajmuje to co najmniej 1 sekundę.
Użyj metody ForEach List
Innym popularnym sposobem jest użycie <T>Listy. Metoda ForEach(), która pozwala przekazać <T>delegata Akcji, który wywołuje delegata Akcji podczas iteracji przez element<T>.
Jest to <T>wewnętrzna metoda implementacji List, dzięki czemu może bezpośrednio uzyskać dostęp do prywatnych tablic i uniknąć kontroli przepełnienia. Teoretycznie powinno być szybko; Ale w naszym przypadku jest tylko jedna pusta metoda, która może nie działać dobrze przy pełnym wywołaniu inline do metody foreach. Poniżej znajduje się kod źródłowy metody ForEEvery (AEEE), który pokazuje, że nie posiada sprawdzania przebiegu, ale nadal zachowuje jednoczesne sprawdzanie numerów wersji.
Dodatkowo, ponieważ konieczne jest przekazanie delegata do metody ForEEvery w kodzie wywoławczym, będzie ona sprawdzać, czy obiekt delegata w klasie generowania zamknięcia jest za każdym razem pusty, a jeśli nie, to nowy Action<T>(), jak pokazano poniżej:
Przyjrzyjmy się, jak wypada to w porównaniu ze słowem kluczowym foreach pod względem wydajności. Poniższy obraz przedstawia wyniki testu referencyjnego:
Sądząc po wynikach testów, jest o 40% wolniejszy niż bezpośrednie użycie słowa kluczowego foreach, wydaje się, że jeśli nie jest konieczne, lepiej jest użyć foreach bezpośrednio, więc czy jest jakiś szybszy sposób?
dla przechodzenia pętli
Wracając do naszego najstarszego sposobu, czyli używania słowa klucza for do przemieszczania się po kolekcji. Powinna to być obecnie najlepiej działająca metoda przeszukiwania, ponieważ nie wymaga jakiegoś redundantnego kodu jak poprzednie (choć indekser jest też sprawdzany, by zapobiec przepełnieniom), a oczywiście nie sprawdza numeru wersji, więc w środowisku wielowątkowym kolekcja jest zmieniana i nie będzie wyjątku przy użyciu for. Kod testowy wygląda tak:
Zobaczymy, jak to wyjdzie.
Wygląda na to, że tak się tego spodziewamy.Bezpośrednie użycie pętli for jest o 60% szybsze niż foreach, zestaw, który kiedyś zajmował 1 sekundę, teraz zajmuje tylko 400 milisekund. Czy jest szybszy sposób?
Użycie Kolekcji Marszałek
Po .NET5 społeczność dotnet wdrożyła klasę CollectionsMarshal, aby poprawić wydajność operacji kolekcji. Ten kurs implementuje dostęp do natywnych tablic typów kolekcji (jeśli widziałeś mój [. .NET Performance Optimization – powinieneś ustawić początkowy rozmiar dla typów kolekcji, wiesz, że podstawową implementacją wielu struktur danych są tablice). Dzięki temu może pominąć różne detekcje i bezpośrednio uzyskać dostęp do oryginalnej tablicy, która powinna być najszybsza. Kod wygląda tak:
Widać, że kod generowany przez kompilator jest bardzo wydajny.
Bezpośredni dostęp do bazowej macierzy jest bardzo niebezpieczny, musisz wiedzieć, co robisz z każdą linijką kodu i mieć wystarczająco dużo testów. Wyniki benchmarku przedstawiają się następująco:
WowKorzystanie z CollectionsMarshal jest o 79% szybsze niż foreach, ale to powinien być powód optymalizacji JIT, nie ma dużej różnicy między użyciem foreach a for keyword loop Span.
streszczenie
Dziś rozmawiałem z Wami o tym, jak szybko przechodzić przez kolekcję List, a w większości przypadków zaleca się używanie słowa kluczowego foreach, które posiada zarówno overflow checking, jak i wielowątkową kontrolę numerów wersji, co ułatwia nam pisanie poprawnego kodu.
Jeśli potrzebujesz wysokiej wydajności i dużych ilości danych, zaleca się bezpośrednie korzystanie z for and CollectionsMarshal.AsSpan do przeglądania kolekcji.
Link do kodu źródłowego tego artykułu:
Logowanie do linku jest widoczne.
Oryginalny link:Logowanie do linku jest widoczne.
|