Krótkie wprowadzenie
Optymalizacja wydajności polega na zapewnieniu, że ta sama liczba żądań jest przetwarzana przy mniejszej liczbie zasobów, które zazwyczaj stanowią procesor lub pamięć, a oczywiście obsługuje układ operacyjny IO, ruch sieciowy, zużycie dysku itd. Ale najczęściej zmniejszamy zużycie CPU i pamięci. Wcześniej udostępniane treści mają pewne ograniczenia, trudno je bezpośrednio przekształcić, dziś chcę podzielić się z Wami prostą metodą – wystarczy zastąpić kilka typów kolekcji, aby osiągnąć efekt poprawy wydajności i zmniejszenia zużycia pamięci. Dziś chcę podzielić się z Wami biblioteką klasową, tymBiblioteka klasowa nazywa się Collections.Pooled, Jak widać po nazwie, jest to pamięć pulowa, aby zmniejszyć zużycie pamięci i GC, a my bezpośrednio zobaczymy, jak wygląda jego wydajność, a także zabierzemy Cię do kodu źródłowego, dlaczego przynosi on te poprawy wydajności.
Zbiory zbiorów
Link do projektu:Logowanie do linku jest widoczne.
Biblioteka opiera się na klasach System.Collections.Generic, które zostały zmodyfikowane, aby wykorzystać nowe <T>biblioteki klas System.Span i System.Buffers.ArrayPool <T>w celu ograniczenia alokacji pamięci, poprawy wydajności oraz umożliwienia większej interoperacyjności z nowoczesnymi API. Collections.Pooled obsługuje .NET Standnd 2.0 (.NET Framework 4.6.1+), a także obsługuje . NET Core 2.1+. Obszerny zestaw testów jednostkowych i benchmarków został przeniesiony z CoreFX.
Łączna liczba testów: 27501. Źródło: 27501. Niepowodzenie: 0. Pominięcie: 0. Test zakończył się sukcesem. Czas wykonania testu: 9,9019 sekundy
Jak używać
Możesz łatwo zainstalować tę bibliotekę przez Nuget, wersję NuGet.
W bibliotece Collections.Pooled implementuje wersje poolowane dla typów kolekcji, których najczęściej używamy, co pokazuje porównanie z natywnymi typami .NET.
| . .NET natywnie | Zbiory zbiorów | uwaga | | Lista<T> | PooledList<T> | Klasy zbiorów ogólnych | | Słownik<TKey, TValue> | PooledDictionary<TKey, TValue> | Klasa słownika ogólnego | | HashSet<T> | PooledSet<T> | Ogólne klasy kolekcji skrótów | | Stos<T> | Stos<T> | Stosy ogólne | | Kolejka<T> | PooledQueue<T> | Kohorta generyczna |
Używając , wystarczy dodać odpowiadające . .NET natywna wersja z wersją Collections.Pooled, jak pokazano w poniższym kodzie:
Należy jednak zauważyć, że typ Pooled implementuje interfejs IDispose, który zwraca używaną pamięć do puli za pomocą metody Dispose(), więc musimy wywołać jej metodę Dispose() po użyciu obiektu kolekcji Pooled. Możesz też użyć słowa kluczowego using var bezpośrednio.
Uwaga: Użyj obiektu kolekcji wewnątrz Collections.PooledNajlepiej jest zwolnić go ręcznie, ale nie ma znaczenia, jeśli go nie zwolnisz, GC ostatecznie go zrecykluje, ale nie da się go zwrócić do puli i nie pozwoli to na oszczędność pamięci. Ponieważ ponownie wykorzystuje przestrzeń pamięci, zwracając ją do puli, musi przetwarzać elementy kolekcji i udostępnia enumerację o nazwie ClearMode, zdefiniowaną następująco:
Domyślnie możesz użyć wartości domyślnej Auto, a jeśli są specjalne wymagania wydajnościowe, możesz użyć Never po poznaniu ryzyka. Dla typów referencji i typów wartości zawierających typy referencji musimy opróżnić referencję tablicy podczas przywracania przestrzeni pamięci do puli; jeśli nie zostanie oczyszczona, GC nie będzie w stanie zwolnić tej części pamięci (ponieważ referencja elementu zawsze była przechowywana przez pulę), jeśli jest to czysty typ wartości, nie można go opróżnić; w tym artykule opisuję różnicę w pamięci między typami odniesień a tablicami struktur (typu wartości), czyste typy wartości nie mają recyklingu nagłówków obiektów i nie wymagają interwencji GC.
. .NET Performance Optimization – Używaj klas alternatywnych dla struktur:Logowanie do linku jest widoczne.
Porównanie wydajności
Nie robiłem Benchmarku sam, a wyniki bieżące projektów open source, z których korzystałem bezpośrednio, wynosiły 0 pod względem zużycia pamięci wielu projektów, co wynikało z braku dodatkowej alokacji pamięci w puli.
PooledList<T>
Przejdź przez elementy z 2048 roku dodane do zestawu w Benchmark. .NET native List <T>wymaga 110 USD (według rzeczywistych wyników benchmarków, milisekundy na rysunku powinny być błędem administracyjnym) i 263KB pamięci, podczas gdy PooledList <T>potrzebuje tylko 36 USD i 0KB pamięci.
PooledDictionary<TKey, TValue>
Dodaj 10_0000 elementów do słownika w pętli w Benchmark, . .NET natywny słownik<TKey, TValue> wymaga 11ms i 13MB pamięci, natomiast PooledDictionary<TKey, TValue> wymaga tylko 7ms i 0MB pamięci.
PooledSet<T>
Przejdź przez kolekcję skrótów w Benchmarku, dodaj 10_0000 elementów, . Natywny .NET HashSet <T>wymaga 5348ms i 2MB, natomiast PooledSet <T>wymaga tylko 4723ms i 0MB pamięci.
PooledStack<T>
Przejdź przez stos w Benchmarku, aby dodać 10_0000 elementów. Natywny .NET PooledStack <T>wymaga 1079ms i 2MB, natomiast PooledStack <T>wymaga tylko 633ms i 0MB pamięci.
PooledQueue<T>
Przewiąż pętlę w Benchmarku, aby dodać 10_0000 elementów do kolejki, . Natywna .NET <T>PooledQueue wymaga 681ms i 1MB, natomiast PooledQueue <T>wymaga tylko 408ms i 0MB pamięci.
Scena nie jest ręcznie wypuszczana
Dodatkowo wspomnieliśmy powyżej, że typ zbioru zbiorów musi zostać zwolniony, ale nie ma znaczenia, jeśli nie zostanie udostępniony, ponieważ GC będzie recyklingował.
Wyniki benchmarku przedstawiają się następująco:
Wnioski można wyciągnąć na podstawie powyższych wyników benchmarków.
Zwolnienie kolekcji typów w Pooled na czas ledwo uruchamia GC i przydziela pamięć, z powyższego wykresu przydziela jedynie 56 bajtów pamięci. Nawet jeśli kolekcja typów Pooled nie zostanie zwolniona, ponieważ alokuje pamięć z puli, nadal będzie ponownie używać pamięci podczas operacji rozbudowy ReSize i pominąć etap inicjalizacji alokacji pamięci GC, który jest stosunkowo szybki. Najwolniejszym jest użycie normalnego typu kolekcji, każda operacja rozszerzenia ReSize musi zastosować nową przestrzeń pamięci, a GC również musi odzyskać poprzednią przestrzeń pamięci.
Analiza zasad
Jeśli czytałeś mój poprzedni wpis na blogu, powinieneś ustawić początkowy rozmiar dla typów kolekcji i przeanalizować zasadę implementacji słownika C#. Możesz wiedzieć, że programiści .NET BCL używają podstawowych struktur danych tych podstawowych typów kolekcji jako tablic do wysokowydajnego dostępu losowego, weźmy na <T>przykład List.
Stwórz nową tablicę do przechowywania dodanych elementów. Jeśli w tablicy nie ma wystarczająco dużo miejsca, operacja rozszerzania jest uruchamiana, aby zażądać dwukrotności przestrzeni. Kod konstruktora wygląda następująco, a widać, że jest to generyczna tablica utworzona bezpośrednio:
Jeśli chcesz zgrupować pamięć, wystarczy zmienić miejsce, gdzie nowa aplikacja kluczowa jest używana w bibliotece klas, aby używać aplikacji pooled. Tutaj się tym z wami dzielę. NET BCL to typ zwany ArrayPool, który zapewnia pulę zasobów tablicy z wielokrotnego użytku ogólnych instancji, co może być używane do zmniejszenia obciążenia GC i poprawy wydajności w przypadku częstego tworzenia i niszczenia tablic.
Podstawową warstwą naszego typu Pooled jest użycie ArrayPool do udostępniania pul zasobów, a z jego konstruktora widać, że domyślnie używa ArrayPool<T>. Udostępnia ją do przypisywania obiektów tablicy, a oczywiście możesz też stworzyć własny ArrayPool, aby z niego korzystać.
Dodatkowo, podczas wykonywania operacji regulacji pojemności (rozbudowy), stara tablica jest zwracana do puli wątków, a nowa tablica również pobierana z puli.
Ponadto autor używa Span do optymalizacji API takich jak Add i Insert, aby zapewnić im lepszą wydajność dostępu losowego. Dodatkowo dodano API z serii TryXXX, dzięki czemu możesz korzystać z niego w wygodniejszy sposób. Na przykład <T>klasa List <T>ma do 170 modyfikacji w porównaniu do PooledList.
streszczenie
W naszym rzeczywistym użyciu online możemy zastąpić natywny typ kolekcji typem kolekcji oferowanym przez Pooled, co bardzo pomaga w redukcji zużycia pamięci i opóźnienia P95. Poza tym, nawet jeśli zapomnisz o wydaniu, wydajność nie będzie dużo gorsza niż przy użyciu natywnego typu kolekcji. Oczywiście najlepszym nawykiem jest uwolnić go z czasem.
Oryginał:Logowanie do linku jest widoczne.
|