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.
Samouczek: zmniejsz alokacje pamięci z użyciem bezpieczeństwa
Często dostrajanie wydajności aplikacji .NET obejmuje dwie techniki. Najpierw zmniejsz liczbę i rozmiar alokacji sterty. Po drugie zmniejsz częstotliwość kopiowania danych. Program Visual Studio udostępnia doskonałe narzędzia , które ułatwiają analizowanie sposobu używania pamięci przez aplikację. Po ustaleniu, gdzie aplikacja wykonuje niepotrzebne alokacje, należy wprowadzić zmiany w celu zminimalizowania tych alokacji. Konwertujesz typy class na typy struct. Używasz funkcji refbezpieczeństwa do zachowywania semantyki i minimalizowania dodatkowego kopiowania.
Użyj programu Visual Studio 17.5, aby uzyskać najlepsze doświadczenie z tym samouczkiem. Narzędzie alokacji obiektów platformy .NET używane do analizowania użycia pamięci jest częścią programu Visual Studio. Możesz użyć programu Visual Studio Code i wiersza polecenia, aby uruchomić aplikację i wprowadzić wszystkie zmiany. Nie będzie jednak można wyświetlić wyników analizy zmian.
Aplikacja, której będziesz używać, to symulacja aplikacji IoT, która monitoruje kilka czujników, aby określić, czy intruz wszedł do tajnej galerii z kosztownościami. Czujniki IoT stale wysyłają dane, które mierzy mieszankę tlenu (O2) i dwutlenku węgla (CO2) w powietrzu. Zgłaszają również temperaturę i względną wilgotność. Każda z tych wartości zmienia się nieznacznie przez cały czas. Jednak gdy osoba wchodzi do pokoju, zmiana jest nieco większa, a co zawsze dzieje się w tym samym kierunku: tlen zmniejsza się, dwutlenek węgla zwiększa się, temperatura wzrasta, podobnie jak wilgotność względna. Kiedy czujniki wykrywają wzrost, uruchamiany jest alarm włamania.
W tym samouczku uruchomisz aplikację, zmierzysz alokacje pamięci, a następnie poprawisz wydajność, zmniejszając liczbę alokacji. Kod źródłowy jest dostępny w przeglądarce przykładów.
Eksplorowanie aplikacji początkowej
Pobierz aplikację i uruchom przykład startowy. Aplikacja startowa działa prawidłowo, ale ponieważ przydziela wiele małych obiektów z każdym cyklem pomiaru, jej wydajność stopniowo się pogarsza wraz z upływem czasu.
Press <return> to start simulation
Debounced measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906
Average measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906
Debounced measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707
Average measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707
Usunięto wiele wierszy.
Debounced measurements:
Temp: 67.597
Humidity: 46.543%
Oxygen: 19.021%
CO2 (ppm): 429.149
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High
Debounced measurements:
Temp: 67.602
Humidity: 46.835%
Oxygen: 19.003%
CO2 (ppm): 429.393
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High
Możesz zapoznać się z kodem, aby dowiedzieć się, jak działa aplikacja. Główny program uruchamia symulację. Po naciśnięciu <Enter>program tworzy pomieszczenie i zbiera początkowe dane punktu odniesienia:
Console.WriteLine("Press <return> to start simulation");
Console.ReadLine();
var room = new Room("gallery");
var r = new Random();
int counter = 0;
room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
Console.WriteLine();
counter++;
return counter < 20000;
});
Po ustanowieniu tych danych odniesienia uruchamia symulację w pomieszczeniu, gdzie generator liczb losowych określa, czy intruz wszedł do pomieszczenia:
counter = 0;
room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
room.Intruders += (room.Intruders, r.Next(5)) switch
{
( > 0, 0) => -1,
( < 3, 1) => 1,
_ => 0
};
Console.WriteLine($"Current intruders: {room.Intruders}");
Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");
Console.WriteLine();
counter++;
return counter < 200000;
});
Inne typy zawierają pomiary, odbitą miarę, która jest średnią z ostatnich 50 pomiarów, oraz średnią wszystkich wykonanych pomiarów.
Następnie uruchom aplikację przy użyciu narzędzia alokacji obiektów platformy .NET. Upewnij się, że używasz kompilacji Release , a nie kompilacji Debug . W menu Debuguj otwórz profilera wydajności. Zaznacz opcję Śledzenie alokacji obiektów platformy .NET , ale nic innego. Uruchom aplikację do ukończenia. Profiler mierzy alokacje obiektów i raportuje o alokacjach oraz cyklach zarządzania pamięcią. Powinien zostać wyświetlony wykres podobny do poniższego obrazu:
Na poprzednim wykresie pokazano, że praca w celu zminimalizowania alokacji zapewni korzyści z wydajności. Na wykresie obiektów na żywo widać wzór zębów piły. Oznacza to, że tworzone są liczne obiekty, które szybko stają się bezużyteczne. Zostaną one później zebrane, jak pokazano na wykresie delta obiektu. Czerwone słupki skierowane w dół wskazują cykl czyszczenia pamięci.
Następnie przyjrzyj się karcie Alokacje poniżej wykresów. W tej tabeli przedstawiono, jakie typy są przydzielane najwięcej:
Typ System.String jest odpowiedzialny za większość alokacji. Najważniejszym zadaniem powinno być zminimalizowanie częstotliwości przydzielania ciągów. Ta aplikacja stale drukuje wiele sformatowanych danych wyjściowych w konsoli. W tej symulacji chcemy zachować komunikaty, więc skoncentrujemy się na dwóch następnych wierszach: na typie SensorMeasurement i typie IntruderRisk.
Kliknij dwukrotnie na wiersz SensorMeasurement. Zobaczysz, że wszystkie alokacje mają miejsce w metodzie staticSensorMeasurement.TakeMeasurement. Metodę można zobaczyć w poniższym fragmencie kodu:
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
Każda miara przydziela nowy SensorMeasurement obiekt, który jest typem class . Każda utworzona instancja SensorMeasurement powoduje przypisanie pamięci na stercie.
Zmienianie klas na struktury
Poniższy kod przedstawia początkową deklarację :SensorMeasurement
public class SensorMeasurement
{
private static readonly Random generator = new Random();
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
private const double CO2Concentration = 409.8; // increases with people.
private const double O2Concentration = 0.2100; // decreases
private const double TemperatureSetting = 67.5; // increases
private const double HumiditySetting = 0.4500; // increases
public required double CO2 { get; init; }
public required double O2 { get; init; }
public required double Temperature { get; init; }
public required double Humidity { get; init; }
public required string Room { get; init; }
public required DateTime TimeRecorded { get; init; }
public override string ToString() => $"""
Room: {Room} at {TimeRecorded}:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}
Typ został pierwotnie utworzony jako class, ponieważ zawiera wiele double pomiarów. To jest większe niż chciałbyś skopiować w krytycznych punktach wykonawczych. Jednak decyzja ta oznaczała dużą liczbę alokacji. Zmień typ z class na struct.
Zmiana z class na struct wprowadza kilka błędów kompilatora, ponieważ oryginalny kod używał kontroli referencji za pomocą null w kilku miejscach. Pierwsza z nich znajduje się w DebounceMeasurement klasie w metodzie AddMeasurement :
public void AddMeasurement(SensorMeasurement datum)
{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i] is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
}
Typ DebounceMeasurement zawiera tablicę 50 pomiarów. Odczyty czujnika są zgłaszane jako średnia z ostatnich 50 pomiarów. To zmniejsza hałas w odczytach. Przed uzyskaniem pełnych 50 odczytów, te wartości wynoszą null. Kod sprawdza, czy null odniesienie pozwala na zgłaszanie poprawnej średniej podczas uruchamiania systemu. Po zmianie typu SensorMeasurement na strukturę, należy użyć innego testu. Typ SensorMeasurement zawiera string dla identyfikatora pokoju, więc można użyć tego testu zamiast tego.
if (recentMeasurements[i].Room is not null)
Pozostałe trzy błędy kompilatora znajdują się w metodzie, która wielokrotnie wykonuje pomiary w pomieszczeniu:
public void TakeMeasurements(Func<SensorMeasurement, bool> MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}
W metodzie starter zmienna lokalna dla SensorMeasurement jest referencją, która może być zerem:
SensorMeasurement? measure = default;
Teraz, gdy SensorMeasurement jest struct zamiast class, wartość nul jest typem wartości dopuszczanej do wartości null. Możesz zmienić deklarację na typ wartości, aby naprawić pozostałe błędy kompilatora:
SensorMeasurement measure = default;
Po usunięciu błędów kompilatora należy sprawdzić kod, aby upewnić się, że semantyka nie uległa zmianie. Ponieważ typy struct są przekazywane przez wartość, zmiany wprowadzone w parametrach metody nie są widoczne po zakończeniu działania metody.
Ważne
Zmiana typu z class na struct może zmienić semantykę programu. Po przekazaniu typu class do metody, wszelkie mutacje wykonane w metodzie są wykonywane na argument. Gdy typ struct jest przekazywany do metody, mutacje wykonane w tej metodzie są stosowane na kopii argumentu. Oznacza to, że każda metoda, które z założenia modyfikują swoje argumenty, powinna zostać zaktualizowana w celu użycia modyfikatora ref dla dowolnego typu argumentu, który został zmieniony z class na struct.
Typ SensorMeasurement nie zawiera żadnych metod, które zmieniają stan, więc nie jest to problem w tym przykładzie. Możesz to udowodnić, dodając readonly modyfikator do SensorMeasurement struktury:
public readonly struct SensorMeasurement
Kompilator wymusza readonly charakter SensorMeasurement struktury. Jeśli inspekcja kodu przegapiła jakąś metodę, która zmieniła stan, kompilator poinformuje cię. Aplikacja nadal kompiluje się bez błędów, więc ten typ to readonly. Dodanie modyfikatora readonly podczas zmiany typu z class na struct może pomóc w znalezieniu członków, którzy modyfikują stan struct.
Unikaj tworzenia kopii
Usunięto dużą liczbę niepotrzebnych alokacji z aplikacji. Typ SensorMeasurement nie pojawia się nigdzie w tabeli.
Teraz wykonuje dodatkowe operacje kopiowania struktury SensorMeasurement za każdym razem, gdy jest używana jako parametr lub jako wartość zwracana. Struktura SensorMeasurement zawiera cztery podwojenia, a DateTime i string. Struktura ta jest wyraźnie większa niż wzorzec. Dodajmy modyfikatory ref lub in do miejsc, w których używany jest typ SensorMeasurement.
Następnym krokiem jest znalezienie metod, które zwracają pomiar, lub przyjmują pomiar jako argument, oraz użycie odwołań tam, gdzie to możliwe. Zacznij od SensorMeasurement struktury. Metoda statyczna TakeMeasurement tworzy i zwraca nowy SensorMeasurementelement :
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
Pozostawimy ten, tak jak to jest, zwracając wartość. Jeśli podjęto próbę zwrócenia przy użyciu ref, zostanie wyświetlony błąd kompilatora. Nie można zwrócić obiektu ref do nowej struktury utworzonej lokalnie w metodzie . Projekt niezmiennej struktury oznacza, że można ustawić tylko wartości miary w konstrukcji. Ta metoda musi utworzyć nową strukturę miary.
Przyjrzyjmy się ponownie DebounceMeasurement.AddMeasurement. Należy dodać in modyfikator do parametru measurement :
public void AddMeasurement(in SensorMeasurement datum)
{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i].Room is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
}
Oszczędza jedną operację kopiowania. Parametr in jest odwołaniem do kopii utworzonej już przez obiekt wywołujący. Możesz również zapisać kopię metodą TakeMeasurement w typie Room. Ta metoda ilustruje, jak kompilator zapewnia bezpieczeństwo podczas przekazywania argumentów przez ref.
TakeMeasurement Początkowa metoda w typie Room przyjmuje argument typu Func<SensorMeasurement, bool>. Jeśli spróbujesz dodać in modyfikator lub ref do tej deklaracji, kompilator zgłosi błąd. Nie można przekazać argumentu ref do wyrażenia lambda. Kompilator nie może zagwarantować, że nazwane wyrażenie nie kopiuje odwołania. Jeśli wyrażenie lambda przechwytuje odwołanie, odwołanie może mieć okres istnienia dłuższy niż wartość, do których się odwołuje. Uzyskanie dostępu do niego poza kontekstem bezpiecznym ref spowodowałoby uszkodzenie pamięci. Reguły ref bezpieczeństwa nie zezwalają na to. Więcej informacji można dowiedzieć się na temat funkcji bezpieczeństwa ref.
Zachowywanie semantyki
Ostateczne zestawy zmian nie będą miały poważnego wpływu na wydajność tej aplikacji, ponieważ typy nie są tworzone w ścieżkach gorących. Te zmiany ilustrują niektóre inne techniki, których można użyć w dostrajaniu wydajności. Przyjrzyjmy się początkowej Room klasie:
public class Room
{
public AverageMeasurement Average { get; } = new ();
public DebounceMeasurement Debounce { get; } = new ();
public string Name { get; }
public IntruderRisk RiskStatus
{
get
{
var CO2Variance = (Debounce.CO2 - Average.CO2) > 10.0 / 4;
var O2Variance = (Average.O2 - Debounce.O2) > 0.005 / 4.0;
var TempVariance = (Debounce.Temperature - Average.Temperature) > 0.05 / 4.0;
var HumidityVariance = (Debounce.Humidity - Average.Humidity) > 0.20 / 4;
IntruderRisk risk = IntruderRisk.None;
if (CO2Variance) { risk++; }
if (O2Variance) { risk++; }
if (TempVariance) { risk++; }
if (HumidityVariance) { risk++; }
return risk;
}
}
public int Intruders { get; set; }
public Room(string name)
{
Name = name;
}
public void TakeMeasurements(Func<SensorMeasurement, bool> MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}
}
Ten typ zawiera kilka właściwości. Niektóre są typami class .
Room Tworzenie obiektu obejmuje wiele alokacji. Jeden dla Room samego siebie i jeden dla każdego elementu typu class, który zawiera. Możesz przekonwertować dwie z tych właściwości z typów class na typy struct: typy DebounceMeasurement oraz typy AverageMeasurement. Przeanalizujmy to przekształcenie z wykorzystaniem obu typów.
DebounceMeasurement Zmień typ z a class na struct. Powoduje to wprowadzenie błędu CS8983: A 'struct' with field initializers must include an explicitly declared constructorkompilatora . Można to naprawić, dodając pusty konstruktor bez parametrów:
public DebounceMeasurement() { }
Więcej informacji na temat tego wymagania można dowiedzieć się w artykule dotyczącym struktur w dokumentacji językowej.
Przesłonięcie Object.ToString() nie modyfikuje żadnych wartości struktury. Modyfikator readonly można dodać do tej deklaracji metody. Typ DebounceMeasurement jest modyfikowalny, więc należy zadbać o to, aby modyfikacje nie wpływały na kopie, które są odrzucane. Metoda AddMeasurement modyfikuje stan obiektu. Jest wywoływana z Room klasy w metodzie TakeMeasurements . Te zmiany mają być utrwalane po wywołaniu metody . Możesz zmienić właściwość Room.Debounce, aby zwróciła odwołanie do pojedynczego wystąpienia typu DebounceMeasurement.
private DebounceMeasurement debounce = new();
public ref readonly DebounceMeasurement Debounce { get { return ref debounce; } }
W poprzednim przykładzie wprowadzono kilka zmian. Najpierw właściwość jest właściwością tylko do odczytu, która zwraca odwołanie tylko do odczytu do instancji należącej do tego pokoju. Teraz jest wspierana przez zadeklarowane pole, które jest inicjowane, gdy obiekt jest instancją Room. Po wprowadzeniu tych zmian zaktualizujesz implementację AddMeasurement metody . Używa on prywatnego pola pomocniczego, debounce, a nie właściwości readonly Debounce. W ten sposób zmiany są wprowadzane w pojedynczym wystąpieniu utworzonym podczas inicjalizacji.
Ta sama technika działa z właściwością Average . Najpierw zmodyfikuj AverageMeasurement typ z class na struct, a następnie dodaj modyfikator readonly do metody ToString.
namespace IntruderAlert;
public struct AverageMeasurement
{
private double sumCO2 = 0;
private double sumO2 = 0;
private double sumTemperature = 0;
private double sumHumidity = 0;
private int totalMeasurements = 0;
public AverageMeasurement() { }
public readonly double CO2 => sumCO2 / totalMeasurements;
public readonly double O2 => sumO2 / totalMeasurements;
public readonly double Temperature => sumTemperature / totalMeasurements;
public readonly double Humidity => sumHumidity / totalMeasurements;
public void AddMeasurement(in SensorMeasurement datum)
{
totalMeasurements++;
sumCO2 += datum.CO2;
sumO2 += datum.O2;
sumTemperature += datum.Temperature;
sumHumidity+= datum.Humidity;
}
public readonly override string ToString() => $"""
Average measurements:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}
Następnie zmodyfikujesz klasę Room zgodnie z tą samą techniką, która była używana dla Debounce właściwości . Właściwość Average zwraca referencję do readonly ref pola prywatnego średniego pomiaru. Metoda AddMeasurement modyfikuje pola wewnętrzne.
private AverageMeasurement average = new();
public ref readonly AverageMeasurement Average { get { return ref average; } }
Unikaj boksu
Istnieje jedna ostatnia zmiana w celu zwiększenia wydajności. Głównym programem jest drukowanie statystyk dla pokoju, w tym ocena ryzyka:
Console.WriteLine($"Current intruders: {room.Intruders}");
Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");
Wywołanie wygenerowanych ToString pól wartości wyliczenia. Można tego uniknąć, pisząc nadpisanie w klasie Room, które formatuje ciąg na podstawie wartości szacowanego ryzyka:
public override string ToString() =>
$"Calculated intruder risk: {RiskStatus switch
{
IntruderRisk.None => "None",
IntruderRisk.Low => "Low",
IntruderRisk.Medium => "Medium",
IntruderRisk.High => "High",
IntruderRisk.Extreme => "Extreme",
_ => "Error!"
}}, Current intruders: {Intruders.ToString()}";
Następnie zmodyfikuj kod w głównym programie, aby wywołać tę nową ToString metodę:
Console.WriteLine(room.ToString());
Uruchom aplikację przy użyciu profilera i przyjrzyj się zaktualizowanej tabeli alokacji.
Usunąłeś wiele alokacji, dzięki czemu aplikacja działa wydajniej.
Korzystanie z bezpieczeństwa referencji w aplikacji
Techniki te służą do dostrajania wydajności na niskim poziomie. Mogą one zwiększyć wydajność aplikacji po zastosowaniu do ścieżek gorących i po zmierzeniu wpływu przed zmianami i po nich. W większości przypadków cykl, który należy wykonać, to:
- Pomiar alokacji: Ustal, które typy są przypisywane najczęściej, oraz kiedy można zredukować alokacje sterty.
-
Konwertowanie klasy na strukturę: wiele razy typy można konwertować z klasy
classna .structAplikacja używa miejsca na stosie zamiast przydzielania sterty. -
Zachowaj semantykę: Konwertowanie elementu
classna elementstructmoże mieć wpływ na semantyka parametrów i zwracanych wartości. Każda metoda, która modyfikuje jego parametry, powinna teraz oznaczać te parametry modyfikatoremref. Dzięki temu modyfikacje są wprowadzane do poprawnego obiektu. Podobnie, jeśli wartość zwracana przez właściwość lub metodę powinna zostać zmodyfikowana przez obiekt wywołujący, należy ją oznaczyć modyfikatoremref. -
Unikaj kopiowania: po przekazaniu dużej struktury jako parametru można oznaczyć parametr modyfikatorem
in. Możesz przekazać referencję w mniejszej liczbie bajtów oraz zapewnić, że metoda nie zmieni oryginalnej wartości. Możesz również zwrócić wartości poprzezreadonly ref, aby zwrócić odwołanie, którego nie można zmodyfikować.
Korzystając z tych technik, możesz zwiększyć wydajność w gorących ścieżkach kodu.