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.
Ważne
Techniki opisane w tej sekcji zwiększają wydajność w przypadku zastosowania do ścieżek gorących w kodzie. Gorące ścieżki to te sekcje kodu źródłowego, które są wykonywane często i wielokrotnie w normalnych operacjach. Zastosowanie tych technik do kodu, który nie jest często wykonywany, będzie miało minimalny wpływ. Przed wprowadzeniem jakichkolwiek zmian w celu zwiększenia wydajności kluczowe jest mierzenie punktu odniesienia. Następnie przeanalizuj stan bazowy, aby określić, gdzie występują problemy z pamięcią. Informacje na temat wielu narzędzi międzyplatformowych umożliwiają mierzenie wydajności aplikacji w sekcji Diagnostyka i instrumentacja. Możesz przećwiczyć sesję profilowania w samouczku w celu mierzenia użycia pamięci w dokumentacji programu Visual Studio.
Po zmierzeniu użycia pamięci i ustaleniu, że można zmniejszyć alokacje, użyj technik w tej sekcji, aby zmniejszyć alokacje. Po każdej kolejnej zmianie ponownie zmierz użycie pamięci. Upewnij się, że każda zmiana ma pozytywny wpływ na użycie pamięci w aplikacji.
Praca z wydajnością na platformie .NET często oznacza usunięcie alokacji z kodu. Każdy przydzielony blok pamięci musi zostać ostatecznie zwolniony. Mniejsza liczba alokacji skraca czas poświęcany na zbieranie danych śmieci. Umożliwia bardziej przewidywalny czas wykonywania, poprzez eliminację mechanizmów zbierania śmieci z określonych ścieżek kodu.
Typową taktyką redukcji alokacji jest zmiana krytycznych struktur danych z class typów na struct typy. Ta zmiana ma wpływ na semantyka używania tych typów. Parametry i wartości zwracane są teraz przekazywane przez wartość zamiast przez referencję. Koszt kopiowania wartości jest niewielki, jeśli typy są małe, trzy wyrazy lub mniej (biorąc pod uwagę, że jedno słowo ma naturalny rozmiar jednej liczby całkowitej). Jest to wymierne i może mieć rzeczywisty wpływ na wydajność dla większych typów. Aby zwalczać efekt kopiowania, deweloperzy mogą przekazać te typy za pomocą ref, aby uzyskać zamierzoną semantykę.
Funkcje języka C# ref umożliwiają wyrażanie żądanych semantyki typów struct bez negatywnego wpływu na ich ogólną użyteczność. Przed tymi ulepszeniami deweloperzy musieli uciekać się do unsafe konstrukcji ze wskaźnikami i nieprzetworzonymi pamięciami, aby osiągnąć ten sam wpływ na wydajność. Kompilator generuje weryfikowalny bezpieczny kod dla nowych ref powiązanych funkcji.
Weryfikowalny bezpieczny kod oznacza, że kompilator wykrywa możliwe przepełnienia buforu lub uzyskuje dostęp do nieprzydzielonej lub zwolnionej pamięci. Kompilator wykrywa i zapobiega niektórym błędom.
Przekazywanie i zwracanie przez referencję
Zmienne w języku C# przechowują wartości. W struct typach wartość jest zawartością wystąpienia typu. W class typach wartość jest odwołaniem do bloku pamięci, który przechowuje wystąpienie typu. Dodanie modyfikatora ref oznacza, że zmienna przechowuje referencję do wartości. W struct typach, odwołanie wskazuje pamięć zawierającą wartość. W class typach odwołanie wskazuje na przestrzeń zawierającą odwołanie do bloku pamięci.
W języku C#parametry metod są przekazywane przez wartość, a zwracane wartości są zwracane przez wartość. Wartość argumentu jest przekazywana do metody . Wartość argumentu zwracanego to wartość zwracana.
Modyfikator ref, in, ref readonlylub out wskazuje, że argument jest przekazywany przez odwołanie. Do metody przekazywane jest odwołanie do lokalizacji magazynu. Dodanie ref do podpisu metody oznacza, że zwracana wartość jest zwracana przez odwołanie.
Odwołanie do lokalizacji przechowywania jest wartością zwracaną.
Możesz również użyć przypisania ref , aby zmienna odwołyła się do innej zmiennej. Typowe przypisanie polega na skopiowaniu wartości z prawej strony do zmiennej znajdującej się po lewej stronie przypisania.
Przypisanie ref kopiuje lokalizację pamięci zmiennej po prawej stronie do zmiennej po lewej stronie. Teraz ref odwołuje się do oryginalnej zmiennej:
int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment
Console.WriteLine(location); // output: 42
sameLocation = 19; // assignment
Console.WriteLine(anInteger); // output: 19
Podczas przypisywania zmiennej zmieniasz jej wartość. Kiedy przypisujesz ref do zmiennej, zmieniasz to, do czego się odnosi.
Możesz pracować bezpośrednio z magazynem dla wartości, używając zmiennych ref, przekazywania przez odwołanie i przypisywania ref. Reguły zakresu wymuszane przez kompilator zapewniają bezpieczeństwo podczas bezpośredniej pracy z pamięcią.
Modyfikatory ref readonly i in wskazują, że argument powinien zostać przekazany przez odwołanie i nie można go ponownie przypisać w metodzie . Różnica polega na tym, że ref readonly metoda używa parametru jako zmiennej. Metoda może przechwytywać parametr, albo zwracać go jako odwołanie tylko do odczytu. W takich przypadkach należy użyć ref readonly modyfikatora.
in W przeciwnym razie modyfikator zapewnia większą elastyczność. Nie musisz dodawać modyfikatora in do argumentu używanego w parametrze in, więc możesz bezpiecznie zaktualizować istniejące sygnatury API za pomocą modyfikatora in. Kompilator wyświetla ostrzeżenie, jeśli nie dodasz ref ani in modyfikatora do argumentu dla parametru ref readonly .
Kontekst bezpieczny ref
C# obejmuje zasady dla wyrażeń ref, aby zapewnić, że wyrażenie ref nie można uzyskać dostępu, gdy miejsce przechowywania, do którego się odnosi, jest już nieprawidłowe. Rozważmy następujący przykład:
public ref int CantEscape()
{
int index = 42;
return ref index; // Error: index's ref safe context is the body of CantEscape
}
Kompilator zgłasza błąd, ponieważ nie można zwrócić odwołania do zmiennej lokalnej z metody. Obiekt wywołujący nie może uzyskać dostępu do magazynu, do którym się odwołuje.
Kontekst bezpieczny ref definiuje zakres, w którym ref wyrażenie jest bezpieczne do uzyskiwania dostępu do lub modyfikowania. W poniższej tabeli wymieniono konteksty bezpieczne ref dla typów zmiennych.
ref nie można zadeklarować pól w elemencie class lub nie będącym typem ref struct, więc te wiersze nie są w tabeli.
| Deklaracja | bezpieczny kontekst odniesienia |
|---|---|
| inny niż ref — lokalny | blok, w którym zadeklarowana jest zmienna lokalna |
| parametr inny niż ref | aktualna metoda |
ref, , ref readonlyin parametr |
metoda wywoływania |
parametr out |
aktualna metoda |
class pole |
metoda wywoływania |
pole inne niż ref struct |
aktualna metoda |
ref pole ref struct |
metoda wywoływania |
Zmienną można ref zwrócić, jeśli jej kontekst bezpieczny ref jest metodą wywołującą. Jeśli jego kontekst bezpieczny ref jest bieżącą metodą lub blokiem, ref zwracanie jest niedozwolone. Poniższy fragment kodu przedstawia dwa przykłady. Dostęp do pola składowego można uzyskać z zakresu wywołującego metodę, więc kontekst bezpieczny ref pola klasy lub struktury jest metodą wywołującą.
Ustalony kontekst ref dla parametru z modyfikatorami ref lub in obejmuje całą metodę. Oba mogą być ref zwracane z metody składowej:
private int anIndex;
public ref int RetrieveIndexRef()
{
return ref anIndex;
}
public ref int RefMin(ref int left, ref int right)
{
if (left < right)
return ref left;
else
return ref right;
}
Uwaga / Notatka
Gdy modyfikator ref readonly lub in jest stosowany do parametru, ten parametr może być zwracany przez ref readonly, a nie przez ref.
Kompilator zapewnia, że odwołanie nie może uciec od bezpiecznego kontekstu ref. Możesz bezpiecznie użyć ref parametrów, ref return i ref zmiennych lokalnych, ponieważ kompilator wykrywa, jeśli przypadkowo napisałeś kod, w którym można uzyskać dostęp do wyrażenia ref, gdy jego zasoby pamięci są nieprawidłowe.
Bezpieczne struktury kontekstu i ref
ref struct typy wymagają większej liczby reguł, aby zapewnić ich bezpieczne wykorzystanie. Typ ref struct może zawierać ref pola. Wymaga to wprowadzenia bezpiecznego kontekstu. W przypadku większości typów metodą wywołującą jest bezpieczny kontekst. Innymi słowy, wartość, która nie jest ref struct, może być zawsze zwracana z metody.
Nieformalnie bezpieczny kontekst elementu ref struct to zakres, w którym można uzyskać dostęp do wszystkich pól ref . Innymi słowy, jest to skrzyżowanie bezpiecznego kontekstu ref wszystkich pól ref . Następująca metoda zwraca ReadOnlySpan<char> wartość do pola członkowskiego, więc jej bezpieczny kontekst jest metodą:
private string longMessage = "This is a long message";
public ReadOnlySpan<char> Safe()
{
var span = longMessage.AsSpan();
return span;
}
Z kolei poniższy kod generuje błąd, ponieważ ref field element członkowski Span<int> odwołuje się do przydzielonej tablicy stosu liczb całkowitych. Nie może uniknąć tej metody
public Span<int> M()
{
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
return numbers; // Error! numbers can't escape this method.
}
Ujednolicenie typów pamięci
Wprowadzenie System.Span<T> i System.Memory<T> zapewnia ujednolicony model do pracy z pamięcią.
System.ReadOnlySpan<T> i System.ReadOnlyMemory<T> udostępniaj wersje tylko do odczytu na potrzeby uzyskiwania dostępu do pamięci. Wszystkie zapewniają abstrakcję nad segmentem pamięci przechowującym zbiór podobnych elementów. Różnica polega na tym, że Span<T> i ReadOnlySpan<T> są ref struct typami, podczas gdy Memory<T> i ReadOnlyMemory<T> są struct typami. Zakresy zawierają wartość ref field. W związku z tym wystąpienia segmentu nie mogą pozostawić bezpiecznego kontekstu.
Bezpieczny kontekst elementu ref struct to kontekst referencyjny bezpieczny jego ref field. Implementacja Memory<T> i ReadOnlyMemory<T> usuwają to ograniczenie. Służysz się tymi typami do bezpośredniego dostępu do buforów pamięci.
Zwiększanie wydajności dzięki bezpieczeństwu ref
Użycie tych funkcji w celu zwiększenia wydajności obejmuje następujące zadania:
-
Unikaj alokacji: Gdy zmieniasz typ z
classnastruct, zmieniasz sposób jego przechowywania. Zmienne lokalne są przechowywane na stosie. Członkowie są przechowywani wewnętrznie podczas alokacji obiektu kontenera. Ta zmiana oznacza mniej alokacji i zmniejsza pracę modułu odśmiecającego elementy. Może to również zmniejszyć presję na pamięć, co spowoduje, że system usuwania śmieci będzie uruchamiany rzadziej. -
Zachowaj semantykę odwołań: zmiana typu z
classnastructzmienia semantykę przekazywania zmiennej do metody. Kod, który zmodyfikował stan parametrów, wymaga modyfikacji. Teraz, gdy parametr jest parametremstruct, metoda modyfikuje kopię oryginalnego obiektu. Można przywrócić oryginalną semantykę, przekazującrefjako parametr. Po tej zmianie metoda ponownie modyfikuje oryginałstruct. -
Unikaj kopiowania danych: kopiowanie większych
structtypów może mieć wpływ na wydajność w niektórych ścieżkach kodu. Można również dodać modyfikator,refaby przekazać większe struktury danych do metod za pomocą odwołania zamiast wartości. -
Ogranicz modyfikacje: po
structprzekazaniu typu przez odwołanie wywoływana metoda może zmodyfikować stan struktury. Modyfikatorrefmożna zamienić na modyfikatoryref readonlylubin, aby wskazać, że argument nie można go zmodyfikować. Preferujref readonly, gdy metoda przechwytuje parametr lub zwraca go przez odwołanie readonly. Można również tworzyćreadonly structtypy lubstructtypy zreadonlyczłonkami, aby zapewnić większą kontrolę nad tym, które elementystructmożna modyfikować. -
Bezpośrednie manipulowanie pamięcią: niektóre algorytmy są najbardziej wydajne podczas traktowania struktur danych jako bloku pamięci zawierającej sekwencję elementów. Typy
SpaniMemoryzapewniają bezpieczny dostęp do bloków pamięci.
Żadna z tych technik nie wymaga unsafe kodu. W sposób rozsądny można uzyskać charakterystykę wydajności z bezpiecznego kodu, który był wcześniej możliwy tylko przy użyciu niebezpiecznych technik. Techniki możesz wypróbować samodzielnie w samouczku dotyczącym zmniejszenia alokacji pamięci.