Sdílet prostřednictvím


Snížení přidělení paměti pomocí nových funkcí jazyka C#

Důležité

Techniky popsané v této části zlepšují výkon při použití na kritické úseky v kódu. Horké části jsou úseky kódové základny, které se běžně a opakovaně spouští během běžných operací. Použití těchto technik na kód, který se často nespustí, bude mít minimální dopad. Před provedením jakýchkoli změn ke zlepšení výkonu je důležité měřit základní úroveň. Pak analyzujte tuto základní úroveň a zjistěte, kde dochází k úzkým místům paměti. O mnoha nástrojích pro různé platformy, které měří výkon vaší aplikace, najdete v části Diagnostika a instrumentace. V průvodci si můžete procvičit profilovací relaci pro měření využití paměti v dokumentaci Visual Studio.

Jakmile změříte využití paměti a zjistíte, že můžete snížit přidělení, snižte přidělení pomocí technik v této části. Po každé následné změně změřte využití paměti znovu. Ujistěte se, že každá změna má pozitivní vliv na využití paměti ve vaší aplikaci.

Optimalizace výkonu v .NET často znamená odebrání alokací z vašeho kódu. Každý blok paměti, který přidělíte, musí být nakonec uvolněn. Méně alokací zkracuje čas na sběr odpadu. Umožňuje předvídatelnější dobu provádění odstraněním procesů uvolňování paměti z konkrétních částí kódu.

Běžnou taktikou redukce přidělení je změna důležitých datových struktur z class typů na struct typy. Tato změna má vliv na sémantiku použití těchto typů. Parametry a návratové hodnoty se teď předávají hodnotou místo odkazu. Náklady na kopírování hodnoty jsou zanedbatelné, pokud jsou typy malé, tři nebo méně slov (vzhledem k tomu, že jedno slovo je přirozené velikosti jednoho celého čísla). Je měřitelný a může mít skutečný dopad na výkon u větších typů. Aby vývojáři mohli bojovat proti účinku kopírování, mohou tyto typy ref předat, aby získali zpět zamýšlenou sémantiku.

Funkce jazyka C# ref umožňují vyjádřit požadovanou sémantiku pro struct typy bez negativního dopadu na jejich celkovou použitelnost. Před těmito vylepšeními se vývojáři museli uchylovat k unsafe konstruktorům s ukazateli a nezpracovanou pamětí, aby dosáhli stejného dopadu na výkon. Kompilátor generuje ověřitelně bezpečný kód pro nové ref související funkce. Ověřitelně bezpečný kód znamená, že kompilátor detekuje možné přetečení vyrovnávací paměti nebo přístup k nepřidělené nebo uvolněné paměti. Kompilátor zjistí a zabraňuje některým chybám.

Předání a vrácení odkazem

Proměnné v jazyce C# uchovávají hodnoty. V struct typech je hodnota obsahem instance typu. V class typech je hodnota odkazem na blok paměti, který ukládá instanci typu. Přidání modifikátoru ref znamená, že proměnná ukládá odkaz na hodnotu. Ve struct typech odkazuje referenční bod na úložiště obsahující hodnotu. V typech class odkaz směřuje k úložišti obsahujícímu odkaz na blok paměti.

V jazyce C# jsou parametry metod předány podle hodnoty a návratové hodnoty jsou vráceny hodnotou. Hodnota argumentu je předána metodě. Hodnota návratového argumentu je návratová hodnota.

Modifikátor ref, in, ref readonly nebo out označuje, že argument je předán odkazem. Do metody se předá odkaz na umístění úložiště. Přidání ref do podpisu metody znamená, že návratová hodnota je vrácena odkazem. Odkaz na umístění úložiště je návratová hodnota.

Přiřazení ref můžete použít také k tomu, aby proměnná odkazovala na jinou proměnnou. Typické přiřazení zkopíruje hodnotu z pravé strany do proměnné na levé straně přiřazení. Přiřazení odkazu zkopíruje paměťovou adresu proměnné na pravé straně na proměnnou na levé straně. Nyní ref odkazuje na původní proměnnou:

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

Když přiřadíte proměnnou, změníte její hodnotu. Když přiřadíte odkazem proměnnou, změníte, na co odkazuje.

Můžete pracovat přímo s úložištěm pro hodnoty pomocí ref proměnných, předávání podle odkazu a přiřazení pomocí odkazu. Pravidla oboru vynucovaná kompilátorem zajišťují bezpečnost při práci přímo s úložištěm.

ref readonly a in oba modifikátory naznačují, že argument by měl být předán odkazem a nelze jej znovu přiřadit v metodě. Rozdíl je, že ref readonly metoda používá parametr jako proměnnou. Metoda může zachytit parametr, nebo může vrátit parametr jako referenci, pouze pro čtení. V takových případech byste měli použít ref readonly modifikátor. in V opačném případě modifikátor nabízí větší flexibilitu. Modifikátor in není potřeba přidávat k argumentu pro parametr in, takže stávající podpisy rozhraní API lze bezpečně aktualizovat pomocí modifikátoru in. Kompilátor vydá upozornění, pokud do argumentu ref parametru nepřidáte in ani ref readonly modifikátor.

Referenční bezpečný kontext

Jazyk C# obsahuje pravidla pro výrazy, ref aby se zajistilo, že výraz nebude přístupný tam, ref kde úložiště, na které odkazuje, už není platné. Podívejte se na následující příklad:

public ref int CantEscape()
{
    int index = 42;
    return ref index; // Error: index's ref safe context is the body of CantEscape
}

Kompilátor hlásí chybu, protože z metody nelze vrátit odkaz na místní proměnnou. Volající nemá přístup k úložišti, na které se odkazuje. Kontext ref safe definuje obor, ve kterém je ref výraz bezpečný pro přístup nebo úpravu. Následující tabulka uvádí referenční bezpečné kontexty pro typy proměnných . ref pole se nedají deklarovat v class ani v ne-referenčním struct, takže tyto řádky nejsou v tabulce:

Prohlášení Referenční bezpečný kontext
místní bez reference blok, kde je lokální deklarován
parametr nenázvový aktuální metoda
ref, ref readonly, in parametr metoda volání
out parametr aktuální metoda
class pole metoda volání
pole bez odkazu struct aktuální metoda
ref pole ref struct metoda volání

Proměnnou lze ref vrátit, pokud je jejím referenčním bezpečným kontextem volání metoda. Pokud je jeho referenční bezpečný kontext aktuální metodou nebo blokem, ref vrácení je zakázáno. Následující fragment kódu ukazuje dva příklady. K poli člena lze přistupovat z oboru volajícího metody, takže bezpečný referenční kontext pole třídy nebo struktury je volající metoda. Referenční bezpečný kontext pro parametr s modifikátory ref nebo in je celá metoda. Obě je možné ref vrátit z metody člena:

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;
}

Poznámka:

Když je modifikátor ref readonly nebo in použit na parametr, tento parametr může být vrácen pomocí ref readonly, nikoli ref.

Kompilátor zajišťuje, že odkaz nemůže opustit svůj bezpečný kontext pro odkazy. Parametry ref a místní proměnné ref return a ref můžete bezpečně používat, protože kompilátor zjistí, jestli jste náhodou napsali kód, ve kterém by se výraz ref mohl použít, když jeho úložiště není platné.

Bezpečný kontext a referenční struktury

ref struct typy vyžadují více pravidel, aby se zajistilo jejich bezpečné použití. Typ ref struct může obsahovat ref pole. To vyžaduje zavedení bezpečného kontextu. U většiny typů je bezpečný kontext volající metodou. Jinými slovy, hodnota, která není ref struct, může být vždy vrácena z metody.

Neformálně platí, že bezpečný kontext pro ref struct je rozsah, ve kterém jsou dostupná všechna jeho ref pole. Jinými slovy, jedná se o průsečík bezpečného kontextu odkazu všech jeho ref polí. Následující metoda vrátí pole člena ReadOnlySpan<char> , takže jeho bezpečný kontext je metoda:

private string longMessage = "This is a long message";

public ReadOnlySpan<char> Safe()
{
    var span = longMessage.AsSpan();
    return span;
}

Naproti tomu následující kód vygeneruje chybu, protože ref field člen Span<int> odkazuje na pole přidělené zásobníku celých čísel. Nelze se vyhnout metodě:

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

Sjednocení typů paměti

Zavedení System.Span<T> a System.Memory<T> poskytují jednotný model pro práci s pamětí. System.ReadOnlySpan<T> a System.ReadOnlyMemory<T> poskytují verze pouze pro čtení pro přístup k paměti. Všechny poskytují abstrakci nad blokem paměti, který ukládá pole podobných prvků. Rozdíl je v tom, že Span<T> a ReadOnlySpan<T> jsou ref struct typy, zatímco Memory<T> a ReadOnlyMemory<T> jsou struct typy. Rozpětí obsahují ref field. Instance úseku proto nemohou opustit svůj bezpečný kontext. Bezpečným kontextemref struct je ref bezpečný kontext jeho ref field. Implementace Memory<T> a ReadOnlyMemory<T> odstraňují toto omezení. Tyto typy slouží k přímému přístupu k paměťovým bufferům.

Zvýšení výkonu s využitím bezpečnosti ref

Použití těchto funkcí ke zlepšení výkonu zahrnuje tyto úlohy:

  • Vyhněte se alokací: Když změníte typ z class na struct, změníte způsob jeho uložení. Místní proměnné jsou uloženy v zásobníku. Členové se ukládají v textu při přidělení objektu kontejneru. Tato změna znamená méně alokací, což sníží množství práce, kterou systém uvolňování paměti vykoná. Může také snížit zatížení paměti, aby garbage collector běžel méně často.
  • Zachování sémantiky odkazu: Změna typu z class na struct může změnit sémantiku předání proměnné metodě. Kód, který změnil stav parametrů, potřebuje upravit. Nyní, když je parametr struct, metoda upravuje kopii původního objektu. Původní sémantiku můžete obnovit předáním parametru jako parametru ref . Po této změně metoda znovu upraví původní struct .
  • Vyhněte se kopírování dat: Kopírování větších struct typů může mít vliv na výkon v některých cestách kódu. Můžete také přidat modifikátor ref pro předání větších datových struktur metodám odkazem místo podle hodnoty.
  • Omezit úpravy: Pokud je typ struct předán odkazem, volaná metoda může změnit stav struktury. Modifikátor ref můžete nahradit ref readonly modifikátory nebo in modifikátory, které indikují, že argument nelze upravit. Preferujte ref readonly, když metoda zachytí parametr nebo ho vrátí prostřednictvím referencí jen pro čtení. Můžete také vytvářet readonly struct typy nebo struct typy se readonly členy, abyste měli větší kontrolu nad tím, jaké členy struct lze upravovat.
  • Přímá manipulace s pamětí: Některé algoritmy jsou nejúčinnější při zpracování datových struktur jako bloku paměti obsahující posloupnost prvků. Typy Span a Memory poskytují bezpečný přístup k blokům paměti.

Žádná z těchto technik nevyžaduje unsafe kód. Používá se moudře, můžete získat charakteristiky výkonu z bezpečného kódu, který byl dříve možný pouze pomocí nebezpečných technik. Techniky můžete vyzkoušet sami v tomto kurzu o snížení přidělení paměti.