Uwaga
Dostęp do tej strony wymaga autoryzacji. Może 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 readonly
lub 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 readonly in 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
class
nastruct
, 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
class
nastruct
zmienia 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ącref
jako parametr. Po tej zmianie metoda ponownie modyfikuje oryginałstruct
. -
Unikaj kopiowania danych: kopiowanie większych
struct
typów może mieć wpływ na wydajność w niektórych ścieżkach kodu. Można również dodać modyfikator,ref
aby przekazać większe struktury danych do metod za pomocą odwołania zamiast wartości. -
Ogranicz modyfikacje: po
struct
przekazaniu typu przez odwołanie wywoływana metoda może zmodyfikować stan struktury. Modyfikatorref
można zamienić na modyfikatoryref readonly
lubin
, 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 struct
typy lubstruct
typy zreadonly
członkami, aby zapewnić większą kontrolę nad tym, które elementystruct
moż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
Span
iMemory
zapewniają 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.