Hur tilldelat minne ska frigöras är föremål för en viss debatt bland programmerare på C-liknande språk. I C och C ++ anses frigörelse av tilldelat minne vara så viktigt att det bör hanteras uttryckligen av programmeraren med hjälp av gratis / radera. I C # och Java anses frigöring av tilldelat minne vara så viktigt att det ska hanteras automatiskt med hjälp av Garbage Collector (GC).

GC underlättar minneshantering, men det har problem.

  • Det använder mer minne. GC kräver extra poäng och referensräknor för varje tilldelning för att göra sitt jobb ordentligt.
  • Lägre prestanda totalt sett. GC tar mer tid att göra sitt arbete än en enkel gratis eller ta bort.
  • Prestanda spikar. När GC körs stannar vanligtvis alla andra trådar tills GC är klar. Detta kan orsaka överhoppade ramar i en grafikapplikation eller oacceptabel fördröjning i tidskritisk kod.

Ännu viktigare är att om du använder C # eller Java är GC en del av din miljö. I den här artikeln vill jag visa dig hur du kan dra nytta av GC och minimera nackdelarna. Låt oss börja.

Första alternativet: Gör ingenting

Det enklaste och enklaste sättet att mikromanera GC är helt enkelt att behandla det som om det inte är ett problem. Detta fungerar eftersom det för det mesta inte kommer att vara ett problem.

GC är bara ett problem om du tilldelar, gratis och sedan omfördelar tusentals av samma objekttyp på kort tid.

Andra alternativet: Tilldela inte så mycket

Titta på din kod och fundera över var du kan återanvända variabler eller inte använda dem alls.

  • Foreach- konstruktionen tilldelar ett objekt för att hålla reda på dess framsteg. Ändra det till ett för .
  • Istället för att skapa ett objekt för returneringsvärdet för en funktion kan du ibland skapa objektet en gång, spara det i en medlemsvariabel och returnera det flera gånger.
  • När det är möjligt, skapa objekt utanför slingor.

Tredje alternativet: Använd en objektpool

Att använda en objektpool kan öka hastigheten på bekostnad av ökad minnesanvändning och kodkomplexitet. Genom att använda en Object Pool, vägrar du några av fördelarna med GC och regresserar från C # eller Java till den lägre nivån på C eller C ++. Denna kraft kan göra en stor skillnad om den används på ett klokt sätt.

Här är vad du vill ha från en objektpool:

  1. Enkelhet . Ett enkelt gränssnitt minimerar kodpåverkan. I synnerhet behöver du i allmänhet inte ett sätt att korsa eller besöka alla föremål som är lagrade i poolen.
  2. Hastighet . Att spara tid är vad poolen handlar om. Det borde vara så snabbt som möjligt. En pool som lagrar tio objekt ska inte fungera annorlunda än en pool som lagrar tio miljoner objekt.
  3. Flexibilitet . Poolen bör låta dig förfördela eller bli av med lagrade föremål efter önskemål.

Med dessa punkter i åtanke, låt oss titta på hur vi kan implementera en Object Pool i C #.

En pool är en stack

En stack är en C # generisk typ som lagrar en samling objekt. För våra ändamål kan du antingen lägga till ett objekt i stacken med Push () eller ta bort ett objekt med Pop (). Dessa två operationer tar konstant tid, vilket betyder att deras prestanda inte ändras med storleken på kollektionen.

 public abstract class Pool {public abstract Type Type {get; }} pool i allmän klass: Pool där T: ny () {Stack tomgång = ny stack (); int max; allmän pool (int max = 10000) {this.max = max; } public T allocate () {return (0 == idle.Count)? ny T (): tomgång.Pop (); } public void deallocate (T t) {if (max> tomgång. Antal) tomgång. Tryck (t); } public void preallocate (int count) {while (count> idle.Count) idle.Push (new T ()); } public void trunkera (int spara) {medan (spara <tomgång.Konto) tomgång.Pop (); } offentligt åsidosättande Type Type {get {return typeof (T); }}} 

I C # måste du definiera basklassen Pool för att behålla en samling pool som har olika T som vi gör nedan.

Använda en pool

Skapa en pool som poolpool = ny pool (). När du vill ha ett nytt objekt av typ T, istället för att säga T t = nytt T () säger du T t = tpool.allocate (). När du är klar med föremålet lägger du tillbaka det i poolen med tpool.deallocate (t).

Lägg pooler i en ordlista

Placera alla dina pooler på en central plats i en ordlista med typ som nyckel.

 statisk klass PoolCentral {statisk ordbokpooler = ny ordbok (); statisk offentlig pool getPool () där T: ny () {if (! pools.ContainKey (typeof (T))) pooler [typeof (T)] = ny pool (); retur (pool) pooler [typeof (T)]; }} 

Unity Prefab Pools

Om du använder Unity och vill skapa prefab-pooler måste du hantera situationen lite annorlunda.

  • Använd objekt i stället för klassen C # Type.
  • Prefabs skapar ett nytt objekt med Instantiate () istället för nytt ().
  • Ring Destroy () för att bli av med instanserade objekt istället för att bara lämna dem till GC.

Lägg bara till följande linjer i PoolCentral och skapa en GoPool-klass.

 statisk ordbok goPools = ny ordbok (); statisk offentlig GoPool getPool (Objekt prefab) {if (! goPools.ContainsKey (prefab)) goPools [prefab] = new GoPool (); returnera goPools [prefab]; } 

Observera att GoPool inte behöver vara generisk eftersom en GoPool alltid lagrar staplar med objekt som returneras från Object.Instantiate (), men du kan göra det generiskt för bekvämlighet och extra säkerhet.

Unity C # Generic Object Pool

Klart

I Java bör du kunna göra samma sak med klass istället för C # -typen.

Som ett sista ord av försiktighet, kom ihåg att initialisera och rensa poolade objekt efter behov. Du kanske vill definiera funktioner med dessa namn i dina poolade typer, ringa initialisera () på objektet efter att ha tilldelat det från poolen och rensa () innan du skickar tillbaka till poolen med deallocate (). Rensa () bör ställa in alla hänvisningsobjektreferenser till noll om du inte vill återanvända dem i poolningsprocessen. Du kan till och med definiera en basklass som innehåller tydlig () och (eftersom den inte kräver några parametrar) ringer den automatiskt från Pool.deallocate ().