A szemétgyűjtés teljesítményének javítása WinUI-alkalmazásokban

A Windows App SDK-val készült és C# nyelven írt WinUI-alkalmazások automatikus memóriakezelést kapnak a .NET szemétgyűjtőtől. Ez a cikk a WinUI-alkalmazások .NET-szemétgyűjtőjének viselkedésével és teljesítményével kapcsolatos ajánlott eljárásokat foglalja össze. A .NET-szemétgyűjtő működéséről és a szemétgyűjtő teljesítményének hibakereséséhez és elemzéséhez szükséges eszközökről további információt a Szemétgyűjtés című témakörben talál.

Megjegyzés:

Ha a szemétgyűjtő alapértelmezett viselkedésébe kell beavatkoznia, az erősen utal az alkalmazás általános memóriaproblémáira. A Visual Studio Memóriahasználat eszközével, valamint a szemétgyűjtés és a teljesítmény útmutatásával azonosíthatja a gyűjteményeket túlélő objektumokat.

A szemétgyűjtő mechanizmus azáltal határozza meg, hogy mikor fut, hogy kiegyensúlyozza a kezelt halom memóriahasználatát azzal a munkamennyiséggel, amelyet a szemétgyűjtés igényel. Az egyik módja annak, hogy a szemétgyűjtő ezt úgy teszi, hogy generációkra osztja a halomot, és az idő nagy részében a halomnak csak egy részét gyűjti. A felügyelt halom három generációból áll:

  • 0. generáció. Ez a generáció újonnan lefoglalt objektumokat tartalmaz, kivéve, ha 85 KB vagy nagyobbak, de ebben az esetben a nagy objektum halom része. A nagyméretű objektumhalmot a 2. generációs gyűjtésekkel gyűjtik össze. A 0. generációs gyűjtemények a leggyakrabban előforduló gyűjteménytípusok, és megtisztítják a rövid élettartamú objektumokat, például a helyi változókat.
  • 1. generáció. Ez a generáció olyan objektumokat tartalmaz, amelyek túlélték a 0. generációs gyűjteményeket. Pufferként szolgál a 0. és a 2. generáció között. Az 1. generációs gyűjtemények ritkábban fordulnak elő, mint a 0. generációs gyűjtemények, és törlik azokat az ideiglenes objektumokat, amelyek az előző 0. generációs gyűjteményekben aktívak voltak. Az 1. generációs gyűjtemény a 0. generációt is gyűjti.
  • 2. generáció. Ez a generáció olyan hosszú élettartamú objektumokat tartalmaz, amelyek túlélték a 0. és az 1. generációs gyűjteményeket. A 2. generációs gyűjtemények a legkevésbé gyakoriak, és összegyűjtik a teljes felügyelt halomot, beleértve a nagy objektum halomot is, amely 85 KB vagy nagyobb objektumokat tartalmaz.

A szemétgyűjtő teljesítményét két szempontból mérheti: a szemétgyűjtéshez szükséges időt és a felügyelt halom memóriahasználatát. Ha egy kisebb, 100 MB-nál kisebb halomméretű alkalmazással rendelkezik, akkor a memóriahasználat csökkentésére kell összpontosítania. Ha egy 100 MB-nál nagyobb felügyelt halommal rendelkező alkalmazással rendelkezik, akkor csak a szemétgyűjtési idő csökkentésére összpontosítson. Az alábbiakban bemutatjuk, hogyan segíthet a .NET szemétgyűjtőnek a jobb teljesítmény elérésében.

Memóriahasználat csökkentése

Kiadási hivatkozások

Az alkalmazás egy objektumára mutató hivatkozás megakadályozza, hogy az objektum és az általa hivatkozott összes objektum összegyűjtve legyen. A .NET fordítója jól észleli, ha egy változó már nincs használatban, így a változó által tárolt objektumok jogosultak lesznek a gyűjteményre. Bizonyos esetekben azonban nem biztos, hogy bizonyos objektumok más objektumokra hivatkoznak, mivel az objektumdiagram egy része az alkalmazás által használt kódtárak tulajdonában lehet. A szemétgyűjtést és a teljesítményt ismertető cikkben megismerheti azokat az eszközöket és technikákat, amelyekkel megállapíthatja, hogy mely objektumok vészelik át a szemétgyűjtést.

Ha hasznos, indukálja a szemétgyűjtést

A szemétgyűjtést csak az alkalmazás teljesítményének mérése után indukálhatja, és megállapította, hogy a gyűjtemény indukálása javítja a teljesítményét.

A GC meghívásával egy generáció szemétgyűjtését indukálhatja . Collect(n), ahol n az a generáció, amelyet össze szeretne gyűjteni (0, 1 vagy 2).

Megjegyzés:

Azt javasoljuk, hogy ne kényszerítse ki a szemétgyűjtést az alkalmazásban, mert a szemétgyűjtő számos heurisztikus eszközzel határozza meg a gyűjtemény végrehajtásának legjobb idejét, és a gyűjtemény kényszerítése sok esetben szükségtelenül használja a CPU-t. Ha azonban tudja, hogy sok olyan objektum van az alkalmazásban, amelyet már nem használ, és vissza szeretné adni ezt a memóriát a rendszernek, akkor célszerű lehet szemétgyűjtést kényszeríteni. Például egy gyűjteményt egy betöltési sorozat végén indukálhat egy játékban, hogy felszabadítsa a memóriát a játék megkezdése előtt.   Ha szeretné elkerülni, hogy véletlenül túl sok szemétgyűjtést indukáljon, állítsa a GCCollectionMode értéketoptimalizáltra. Ez arra utasítja a szemétgyűjtőt, hogy csak akkor indítsa el a gyűjteményt, ha megállapítja, hogy a gyűjtemény elég hatékony lenne ahhoz, hogy indokolt legyen.

Szemétgyűjtési idő csökkentése

Ez a szakasz akkor érvényes, ha elemezte az alkalmazást, és nagy mennyiségű szemétgyűjtési időt figyelt meg. A szemétgyűjtéssel kapcsolatos szüneteltetési idők magukban foglalják az egyetlen szemétgyűjtési bérlet futtatásához szükséges időt, valamint azt, hogy az alkalmazás mennyi időt tölt szemétgyűjtéssel. A gyűjteményhez szükséges idő attól függ, hogy mennyi élő adatot kell elemeznie a gyűjtőnek. A 0. és az 1. generáció mérete korlátozott, de a 2. generáció továbbra is növekszik, mivel a hosszú élettartamú objektumok aktívak az alkalmazásban. Ez azt jelenti, hogy a 0. és az 1. generáció gyűjtési ideje korlátozott, míg a 2. generációs gyűjtemények hosszabb időt is igénybe vehetnek. A szemétgyűjtések futásának gyakorisága többnyire attól függ, hogy mennyi memóriát foglal le, mivel a szemétgyűjtés felszabadítja a memóriát a foglalási kérelmek kielégítése érdekében.

A szemétgyűjtő időnként szünetelteti az alkalmazást a munka elvégzéséhez, de nem feltétlenül szünetelteti az alkalmazást, amikor gyűjteményt végez. A szüneteltetési idők általában nem észlelhetők felhasználó számára az alkalmazásban, különösen a 0. és az 1. generációs gyűjtemények esetében. A . NET-szemétgyűjtő háttérbeli szemétgyűjtési funkciója lehetővé teszi a 2. generációs gyűjtemények egyidejű végrehajtását az alkalmazás futása közben, és csak rövid ideig szünetelteti az alkalmazást. A 2. generációs gyűjtemények azonban nem mindig végezhetők el háttérgyűjteményként. Ebben az esetben a szüneteltetés felhasználó számára érzékelhető lehet, ha elég nagy halom van (több mint 100 MB).

A gyakori szemétgyűjtések hozzájárulhatnak a megnövekedett processzorhasználathoz, a megnövekedett energiafogyasztáshoz, a hosszabb betöltési időkhöz vagy az alkalmazásban a képkockák számának csökkenéséhez. Az alábbiakban néhány technikát használhat a szemétgyűjtési idő és a gyűjtéssel kapcsolatos szüneteltetések csökkentésére a felügyelt WinUI-alkalmazásban.

Memóriafoglalások csökkentése

Ha nem foglal le objektumokat, akkor a szemétgyűjtő csak akkor fog futni, ha kevés memória áll rendelkezésre a rendszerben. A közvetlenül lefoglalt memória mennyiségének csökkentése közvetlenül ahhoz vezet, hogy ritkábban történik szemétgyűjtés.

Ha az alkalmazás egyes szakaszaiban a szüneteltetések teljesen nemkívánatosak, akkor a szükséges objektumokat előre lefoglalhatja egy kevésbé teljesítménykritikus időszakban. Egy játék például lefoglalhatja a játékmenethez szükséges összes objektumot egy szint betöltési képernyője alatt, és nem foglal le semmilyen foglalást a játék során. Ez elkerüli a szüneteket, miközben a felhasználó a játékot játssza, és magasabb és konzisztensebb képkockasebességhez vezethet.

A 2. generációs gyűjtések csökkentése a közepes élettartamú objektumok elkerülésével

A generációs szemétgyűjtések akkor teljesítenek a legjobban, ha valóban rövid élettartamú és/vagy valóban hosszú élettartamú objektumokkal rendelkezik az alkalmazásban. A rövid élettartamú tárgyakat az olcsóbb 0. és az 1. generációs gyűjteményekben gyűjtik össze, és a hosszú élettartamú objektumok a 2. generációra kerülnek elő, amelyeket ritkán gyűjtenek össze. A hosszú élettartamú objektumok azok, amelyek az alkalmazás teljes időtartama alatt vagy az alkalmazás jelentős időszakában, például egy adott oldal vagy játékszint alatt vannak használatban.

Ha gyakran hoz létre olyan objektumokat, amelyek ideiglenes élettartammal rendelkeznek, de elég hosszú ideig élnek ahhoz, hogy a 2. generációba kerüljenek, több költséges 2. generációs kollekció történik. A meglévő objektumok újrahasznosításával vagy az objektumok gyorsabb felszabadításával csökkentheti a 2. generációs gyűjteményeket.

A középtávú élettartamú objektumok gyakori példája az olyan objektumok, amelyek a felhasználó által görgetett listában lévő elemek megjelenítésére szolgálnak. Ha az objektumok akkor jönnek létre, amikor a lista elemei a nézetbe kerülnek görgetés közben, és már nem hivatkoznak rájuk, amikor a lista elemei kikerülnek a nézetből, akkor az alkalmazás általában nagy számú, második generációs gyűjteményeket tartalmaz. Ilyen helyzetekben előre lefoglalhat és újra felhasználhat egy objektumkészletet a felhasználó számára aktívan megjelenített adatokhoz, és rövid élettartamú objektumok használatával betöltheti az adatokat a lista elemeinek megtekintésekor.

A 2. generációs gyűjtemények csökkentése a nagy méretű, rövid élettartamú objektumok elkerülésével

A 85 KB-os vagy annál nagyobb objektumok a nagy objektum halomra (LOH) vannak lefoglalva, és a rendszer a 2. generáció részeként gyűjti össze. Ha 85 KB-nál nagyobb ideiglenes változókkal (például pufferekkel) rendelkezik, akkor a 2. generációs gyűjtemény megtisztítja őket. Az ideiglenes változók 85 KB-nál kisebbre való korlátozása csökkenti a 2. generációs gyűjtemények számát az alkalmazásban. Az egyik gyakori módszer egy pufferkészlet létrehozása és a készletből származó objektumok újrafelhasználása a nagy ideiglenes lefoglalások elkerülése érdekében.

A hivatkozásban gazdag objektumok elkerülése

A szemétgyűjtő az objektumok közötti hivatkozások követésével határozza meg, hogy mely objektumok élnek, kezdve az alkalmazás gyökerétől. További információ: Mi történik a szemétgyűjtés során? Ha egy objektum sok hivatkozást tartalmaz, akkor a szemétgyűjtőnek több munkát kell elvégeznie. Gyakori módszer, különösen a nagy objektumok esetén, hogy hivatkozásokkal nem rendelkező objektumokká konvertálja a hivatkozásban gazdag objektumokat. Például a hivatkozás tárolása helyett tároljon egy indexet. Természetesen ez a technika csak akkor működik, ha logikailag lehetséges.

Az objektumhivatkozások indexekkel való cseréje zavaró és bonyolult változást okozhat az alkalmazásban, és a nagy méretű objektumok esetében a leghatékonyabb, ha sok hivatkozással rendelkezik. Ezt csak akkor tegye, ha hosszú szemétgyűjtési időtartamokat észlel az alkalmazásban a sok hivatkozást tartalmazó objektumok miatt.