Udostępnij za pośrednictwem


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:

Allocation graph for running the intruder alert app before any optimizations.

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:

Chart that shows which types are allocated most frequently.

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 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 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 classstructna .

Zmiana elementu z na classstruct 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ą structclasszamiast , 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 classstruct.

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 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 przez refpolecenie , 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 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ę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 Debouncereadonly . 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.

Allocation graph for running the intruder alert app after modifications.

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 element struct 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 modyfikatorem ref . 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ć modyfikatorem ref .
  • 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.