Megosztás a következőn keresztül:


Memóriafoglalások csökkentése új C#-funkciókkal

Fontos

Az ebben a szakaszban ismertetett technikák javítják a teljesítményt a kód gyakori elérési útjaira alkalmazva. A gyakori elérési utak a kódbázis azon szakaszai, amelyeket a normál műveletek során gyakran és ismétlődően hajtanak végre. Ezeknek a technikáknak a gyakran nem végrehajtott kódokra való alkalmazása minimális hatással lesz. Mielőtt bármilyen módosítást végeznénk a teljesítmény javítása érdekében, kritikus fontosságú az alapkonfiguráció mérése. Ezután elemezze az alapállapotot annak meghatározásához, hogy hol fordulnak elő memória szűk keresztmetszetek. Számos platformfüggetlen eszközzel mérheti az alkalmazás teljesítményét a Diagnosztika és a rendszerállapot című szakaszban. A Visual Studio dokumentációjában a memóriahasználat mérésére vonatkozó oktatóanyagban gyakorolhat profilkészítési munkamenetet.

Miután felmérte a memóriahasználatot, és megállapította, hogy csökkentheti a foglalásokat, az ebben a szakaszban ismertetett technikákkal csökkentheti a foglalásokat. Minden egymást követő módosítás után ismét mérje meg a memóriahasználatot. Győződjön meg arról, hogy minden módosítás pozitív hatással van az alkalmazás memóriahasználatára.

A .NET teljesítménybeli működése gyakran azt jelenti, hogy eltávolítja a foglalásokat a kódból. Végül minden lefoglalt memóriablokkot felszabadítani kell. Kevesebb foglalás csökkenti a szemétgyűjtésben töltött időt. Kiszámíthatóbb végrehajtási időt tesz lehetővé azáltal, hogy eltávolítja a szemétgyűjtéseket bizonyos kódelérési utakról.

A foglalások csökkentésének gyakori taktikája a kritikus adatstruktúrák class típusúakról struct típusúakra való átalakítása. Ez a változás hatással van az ilyen típusú használat szemantikára. A paramétereket és a visszatérési értékeket mostantól érték szerint adjuk át, nem pedig hivatkozással. Az érték másolásának költsége elhanyagolható, ha a típusok kicsik, három szó vagy kevesebb (figyelembe véve, hogy egy szó természetes méretű egy egész szám). Mérhető, és a nagyobb típusok teljesítményére is hatással lehet. A másolás hatásának leküzdése érdekében a fejlesztők át tudják adni ezeket a típusokat ref a tervezett szemantikák visszaállításához.

A C# ref -funkciók lehetővé teszik a típusok kívánt szemantikájának struct kifejezését anélkül, hogy az negatív hatással lenne az általános használhatóságukra. A fejlesztések előtt a fejlesztőknek mutatókkal és nyers memóriával rendelkező szerkezeteket kellett alkalmazniuk unsafe , hogy ugyanazt a teljesítményhatást érjék el. A fordító ellenőrizhetően biztonságos kódot hoz létre az új ref kapcsolódó funkciókhoz. A ellenőrizhetően biztonságos kód azt jelenti, hogy a fordító észleli a lehetséges puffertúlcsordulásokat, illetve a nem foglalt vagy szabad memória elérését. A fordító észleli és megakadályozza a hibákat.

Átadás és visszaadás referenciaként

A C# nyelvben a változók értékeket tárolnak. A struct típusok esetén az érték a típus egy példányának tartalma. Típusok esetén class az érték egy memóriablokkra mutató hivatkozás, amely a típus egy példányát tárolja. A ref módosító hozzáadása azt jelenti, hogy a változó tárolja az értékre mutató hivatkozást . Az struct típusoknál a hivatkozás az értéket tartalmazó tárolóra mutat. A class típusok esetén a referencia a tárolóra mutat, amely tartalmazza a memóriablokkra mutató hivatkozást.

A C#-ban a metódusok paramétereit érték szerint adja át a rendszer, a visszaadott értékeket pedig érték szerint adja vissza. Az argumentum értéke a metódusnak lesz átadva. A visszatérési argumentum értéke a visszatérési érték.

A ref, in, ref readonlyvagy out módosító azt jelzi, hogy az argumentumot hivatkozással adja át. A metódus a tárolási helyre mutató hivatkozást ad át. Ha a metódus szignatúrájához hozzáadja a ref, ez azt jelenti, hogy a visszaadott érték hivatkozásként tér vissza. A tárhelyre mutató hivatkozás a visszatérési érték.

A ref-hozzárendeléssel azt is megteheti, hogy egy változó egy másik változóra hivatkozik. Egy tipikus hozzárendelés a jobb oldali értékeket a hozzárendelés bal oldalán lévő változóba másolja. A ref-hozzárendelés a változó memóriahelyét a jobb oldalon a bal oldalon lévő változóra másolja. A ref most az eredeti változóra hivatkozik:

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

Amikor hozzárendel egy változót, megváltoztatja annak értékét. Amikor újra hozzárendel egy változót, megváltoztatja, amire hivatkozik.

A ref változókkal közvetlenül is dolgozhat az értékek tárolásával, hivatkozás általi továbbítással és ref-hozzárendeléssel. A fordító által érvényesített hatókörszabályok biztosítják a biztonságot tárolókezelés során.

A ref readonly és in módosítók egyaránt jelzik, hogy az argumentumot hivatkozásként kell átadni, és nem lehet újra hozzárendelni a metódusban. A különbség az, hogy azt jelzi, ref readonly hogy a metódus a paramétert változóként használja. A metódus rögzítheti a paramétert, vagy könnyen hivatkozva visszaadhatja a paramétert. Ezekben az esetekben a ref readonly módosító használata ajánlott. Ellenkező esetben a in módosító nagyobb rugalmasságot biztosít. Nem kell hozzáadnia a in módosítót egy in paraméter argumentumához, így a módosító használatával biztonságosan frissítheti a in meglévő API-aláírásokat. A fordító figyelmeztetést ad ki, ha nem adja hozzá az argumentumhoz a ref vagy in módosítót egy ref readonly paraméter esetén.

Biztonságos környezet kontextusa

A C# a kifejezésekre vonatkozó ref szabályokat tartalmaz, amelyek biztosítják, hogy egy ref kifejezés ne legyen elérhető ott, ahol a hivatkozott tároló már nem érvényes. Vegye figyelembe a következő példát:

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

A fordító hibát jelez, mert nem tud helyi változóra mutató hivatkozást visszaadni egy metódusból. A hívó nem tudja elérni a hivatkozott tárolót. A ref safe környezet határozza meg azt a hatókört, amelyben egy ref kifejezés biztonságosan elérhető vagy módosítható. Az alábbi táblázat a változótípusok biztonságos ref-környezeteit sorolja fel. ref mezők nem deklarálhatók class vagy nemReferencia struct-ben, ezért ezek a sorok nem találhatók a táblában.

Nyilatkozat ref biztonságos környezet
nem referencia helyi az a blokk, ahol a lokális változó deklarálva van
nem referencia paraméter aktuális metódus
ref, ref readonly, in paraméter hívási módszer
out paraméter aktuális metódus
class mező hívási módszer
nem referencia struct mező aktuális metódus
ref mezője ref struct hívási módszer

Egy változó akkor adható ref vissza, ha a ref biztonságos környezete a hívási módszer. Ha a ref biztonságos környezete az aktuális metódus vagy blokk, nem engedélyezett a ref visszatérés. Az alábbi kódrészlet két példát mutat be. A tagmezők a metódust hívó hatókörből érhetők el, így az osztály- vagy struktúramező biztonságos környezete a hívási módszer. A , vagy ref módosítókkal rendelkező paraméterek in az egész módszer. Mindkettő visszaadható ref tagmetódusból:

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

Megjegyzés:

Amikor a ref readonly vagy in módosítót alkalmazzák egy paraméterre, az a paraméter ref readonly által adható vissza, és nem ref által.

A fordító biztosítja, hogy egy hivatkozás ne meneküljön el a ref biztonságos környezetéből. Biztonságosan használhatja a ref paramétereket, a ref return, és a ref helyi változókat, mert a fordító automatikusan észleli, ha véletlenül olyan ref kódot ír, amely akkor lenne hozzáférhető, amikor a tárhely érvénytelen.

Biztonságos környezet és referenciastruktúrák

ref struct a típusok több szabályt igényelnek a biztonságos használatuk érdekében. Egy ref struct típus tartalmazhat ref mezőket. Ehhez szükség van egy biztonságos környezet bevezetésére. A legtöbb típus esetében a biztonságos környezet a hívási módszer. Más szóval egy nem egy ref struct érték mindig visszaadható egy metódusból.

Informálisan a biztonságos környezet a ref struct esetében az a hatókör, ahol az összes ref mező elérhető. Más szóval, ez az összes mezőnek a ref biztonságos környezete metszetével azonos. A következő függvény visszatér egy ReadOnlySpan<char> tagmezőre, tehát a biztonságos környezet maga a függvény.

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

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

Ezzel szemben a következő kód hibát ad ki, mert a ref field a Span<int> tag az egész számokat tartalmazó verembe foglalt tömbre hivatkozik. Nem tud kikerülni a metódusból:

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

Memóriatípusok egyesítése

Az System.Span<T> és System.Memory<T> bevezetése egy egységes modellt kínál a memória kezeléséhez. System.ReadOnlySpan<T> és System.ReadOnlyMemory<T> biztosítson olvasható verziókat a memória eléréséhez. Ezek mind absztrakciót biztosítanak egy hasonló elemeket tartalmazó memóriablokk felett. A különbség az, hogy Span<T> és ReadOnlySpan<T>ref struct típusok, míg Memory<T> és ReadOnlyMemory<T>struct típusok. A spanok tartalmaznak egy ref field. Ezért a span példányai nem hagyhatják el a biztonságos környezetet. A biztonságos környezet a ref structref biztonságos környezete.ref field A Memory<T> és ReadOnlyMemory<T> megvalósítása megszünteti ezt a korlátozást. Ezekkel a típusokkal közvetlenül érheti el a memóriapuffereket.

Teljesítmény javítása a ref biztonságával

Az alábbi funkciók a teljesítmény javítása érdekében az alábbi feladatokat foglalják magukban:

  • Kerülje a foglalásokat: Ha egy típust class-ról struct-ra módosít, megváltozik annak tárolási módja. A helyi változók a veremen vannak tárolva. A tagok inline módon vannak tárolva, amikor a tároló objektum lefoglalásra kerül. Ez a változás kevesebb foglalást jelent, és csökkenti a szemétgyűjtő által végzett munkát. Csökkentheti a memóriaterhelést is, így a szemétgyűjtő ritkábban fut.
  • A referencia szemantikájának megőrzése: Egy típus módosítása egy classstruct változó metódusnak való átadásának szemantikáját módosítja. Módosítani kell a paraméterek állapotát módosító kódot. Most, hogy a paraméter egy struct, a metódus módosítja az eredeti objektum másolatát. Az eredeti szemantikát visszaállíthatja úgy, hogy paraméterként adja át a paramétert ref . A módosítás után a metódus újra módosítja az eredetit struct .
  • Kerülje az adatok másolását: A nagyobb struct típusok másolása hatással lehet bizonyos kódútvonalak teljesítményére. Azt is hozzáadhatja a ref módosítóhoz, hogy nagyobb adatstruktúrát adjon át a metódusokhoz hivatkozással, nem pedig érték alapján.
  • Módosítások korlátozása: Ha egy struct típust hivatkozással ad át, a hívott metódus módosíthatja a szerkezet állapotát. Lecserélheti a ref módosítót a ref readonly vagy in módosítókra, hogy jelezze, az argumentum nem módosítható. Előnyben részesítse ref readonly, ha a metódus rögzíti a paramétert, vagy readonly hivatkozással adja vissza. Az readonly struct típusokat vagy struct típusokat létrehozhat, amelyek readonly tagokkal rendelkeznek, hogy jobban szabályozhassa, mely tagjai módosíthatók egy struct-nak.
  • A memória közvetlen kezelése: Egyes algoritmusok akkor a leghatékonyabbak, ha az adatstruktúrákat elemek sorozatát tartalmazó memóriablokkként kezelik. A Span és Memory típusok biztonságosan biztosítanak hozzáférést a memóriablokkokhoz.

Ezen technikák egyike sem igényel unsafe kódot. Okosan használva olyan biztonságos kódból kaphat teljesítményjellemzőket, amelyek korábban csak nem biztonságos technikákkal lehetségesek. A memóriafoglalások csökkentéséről szóló oktatóanyagban saját maga is kipróbálhatja a technikákat.