Samouczek: zmniejszanie alokacji pamięci przy użyciu ref
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. Typy są konwertowane class
na struct
typy. Funkcje bezpieczeństwa służą ref
do zachowywania semantyki i minimalizowania dodatkowego kopiowania.
Użyj programu Visual Studio 17.5 , aby uzyskać najlepsze środowisko z tego samouczka. 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 w celu określenia, czy intruz wszedł do galerii wpisów tajnych z wartoś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 nieco bardziej, i zawsze w tym samym kierunku: Tlen zmniejsza się, dwutlenek węgla zwiększa się, temperatura wzrasta, podobnie jak wilgotność względna. Gdy czujniki łączą się w celu pokazania wzrostu, alarm intruza jest wyzwalany.
W tym samouczku uruchomisz aplikację, wykonasz pomiary alokacji 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, wydajność działa powoli, ponieważ działa w czasie.
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 klawisza <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, odrzuceniętą miarę, która jest średnią z ostatnich 50 pomiarów i ś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 raporty dotyczące alokacji i cykli odzyskiwania 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 zostanie wyświetlony wzorzec 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 różnicowym obiektu. Czerwone słupki w dół wskazują cykl odzyskiwania 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 odpowiada najwięcej alokacji. Najważniejszym zadaniem powinno być zminimalizowanie częstotliwości alokacji 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: SensorMeasurement
typie i typie IntruderRisk
.
SensorMeasurement
Kliknij dwukrotnie wiersz. Zobaczysz, że wszystkie alokacje mają miejsce w metodzie static
SensorMeasurement.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 SensorMeasurement
powoduje alokację sterty.
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 element, class
ponieważ zawiera wiele double
pomiarów. Jest on większy niż chcesz skopiować w gorących ścieżkach. Jednak decyzja ta oznaczała dużą liczbę alokacji. Zmień typ z a class
struct
na .
Zmiana elementu z na class
struct
powoduje wprowadzenie kilku błędów kompilatora, ponieważ oryginalny kod używał null
kontroli odwołań 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 podjęciem pełnych 50 odczytów te wartości to null
. Kod sprawdza, czy odwołanie do null
raportu jest prawidłową średnią podczas uruchamiania systemu. Po zmianie typu na SensorMeasurement
strukturę należy użyć innego testu. Typ SensorMeasurement
zawiera string
identyfikator pokoju, więc można użyć tego testu zamiast:
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
zmiennej jest odwołaniem dopuszczalnym do wartości null:
SensorMeasurement? measure = default;
Teraz, gdy SensorMeasurement
element jest wartością struct
class
zamiast , wartość null może być 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ż struct
typy są przekazywane przez wartość, modyfikacje wprowadzone w parametrach metody nie są widoczne po powrocie metody.
Ważne
Zmiana typu z na class
może struct
zmienić semantyka programu. class
Po przekazaniu typu do metody wszelkie mutacje wykonane w metodzie są wykonywane do argumentu. struct
Gdy typ jest przekazywany do metody, a mutacje wykonane w metodzie są wykonywane do kopii argumentu. Oznacza to, że każda metoda modyfikując jej argumenty według projektu powinna zostać zaktualizowana w celu użycia ref
modyfikatora dla dowolnego typu argumentu, który został zmieniony z typu na class
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 nie powiodła się w kodzie, który zmodyfikował stan, kompilator poinformuje Cię. Aplikacja nadal kompiluje się bez błędów, więc ten typ to readonly
. readonly
Dodanie modyfikatora w przypadku zmiany typu z elementu na class
element struct
może pomóc w znalezieniu elementów członkowskich, które modyfikują stan .struct
Unikaj tworzenia kopii
Usunięto dużą liczbę niepotrzebnych alokacji z aplikacji. Typ SensorMeasurement
nie jest wyświetlany w tabeli w dowolnym miejscu.
Teraz wykonuje dodatkowe operacje kopiowania SensorMeasurement
struktury za każdym razem, gdy jest używany jako parametr lub wartość zwracana. Struktura SensorMeasurement
zawiera cztery podwojenia, a DateTime i string
. Struktura ta jest niezwykle większa niż odwołanie. Dodajmy ref
modyfikatory lub in
do miejsc, w których SensorMeasurement
jest używany typ.
Następnym krokiem jest znalezienie metod, które zwracają pomiar, lub użycie miary jako argumentu i użycie odwołań tam, gdzie to możliwe. Zacznij od SensorMeasurement
struktury. Metoda statyczna TakeMeasurement
tworzy i zwraca nowy SensorMeasurement
element :
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 przez ref
polecenie , 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);
}
Zapisuje jedną operację kopiowania. Parametr in
jest odwołaniem do kopii utworzonej już przez obiekt wywołujący. Możesz również zapisać kopię przy użyciu TakeMeasurement
metody 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 .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 z elementów członkowskich class
typu, który zawiera. Możesz przekonwertować dwie z tych właściwości z class
typów na struct
typy: DebounceMeasurement
typy i AverageMeasurement
. Przepracujmy to przekształcenie przy użyciu 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 constructor
kompilatora . 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ęcia Object.ToString() nie modyfikuje żadnych wartości struktury. Modyfikator można dodać readonly
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ć odwołanie do pojedynczego DebounceMeasurement
wystąpienia typu:
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ą readonly, która zwraca odwołanie readonly do wystąpienia należącego do tego pokoju. Jest ona teraz wspierana przez zadeklarowane pole, które jest inicjowane po utworzeniu Room
wystąpienia obiektu. Po wprowadzeniu tych zmian zaktualizujesz implementację AddMeasurement
metody . Używa on pola prywatnego zaplecza, debounce
, a nie właściwości Debounce
readonly . W ten sposób zmiany są wprowadzane w pojedynczym wystąpieniu utworzonym podczas inicjowania.
Ta sama technika działa z właściwością Average
. Najpierw zmodyfikujesz AverageMeasurement
typ z elementu class
na struct
, a następnie dodaj readonly
modyfikator 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 wartość do readonly ref
pola prywatnego dla średniej miary. 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 przesłonięcia w Room
klasie, która 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.
Usunięto wiele alokacji i udostępniono aplikację z zwiększeniem wydajności.
Korzystanie z bezpieczeństwa ref w aplikacji
Te techniki są dostrajaniem wydajności niskiego poziomu. 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:
- Alokacje miar: określ, jakie typy są przydzielane najwięcej, i kiedy można zmniejszyć alokacje sterty.
- Konwertowanie klasy na strukturę: wiele razy typy można konwertować z klasy
class
na .struct
Aplikacja używa miejsca na stosie zamiast przydzielania sterty. - Zachowaj semantykę: Konwertowanie elementu
class
na elementstruct
moż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 właściwość lub metoda zwracana wartość powinna zostać zmodyfikowana przez obiekt wywołujący, należy je oznaczyć modyfikatoremref
. - Unikaj kopiowania: po przekazaniu dużej struktury jako parametru można oznaczyć parametr modyfikatorem
in
. Odwołanie można przekazać w mniejszej liczbie bajtów i upewnić się, że metoda nie modyfikuje oryginalnej wartości. Możesz również zwrócić wartości,readonly 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.