Vraag Hoe kan ik garbage collection-vertragingen in Java-games voorkomen? (Best Practices) [gesloten]


Ik ben performance-tuning van interactieve games in Java voor het Android-platform. Af en toe is er een hapering in tekenen en interactie voor garbage collection. Meestal is het minder dan een tiende van een seconde, maar soms kan het op 200 meter lang zijn op zeer trage apparaten.

Ik gebruik de ddms profiler (onderdeel van de Android SDK) om uit te zoeken waar mijn geheugentoewijzingen vandaan komen en haal ze uit mijn innerlijke tekening en logische loops.

De ergste dader waren korte loops gedaan zoals,

for(GameObject gob : interactiveObjects)
    gob.onDraw(canvas);

waar elke keer dat de lus werd uitgevoerd er een was iterator toegewezen. Ik gebruik arrays (ArrayList) voor mijn objecten nu. Als ik ooit bomen of hashes in een innerlijke lus wil hebben, weet ik dat ik voorzichtig moet zijn of zelfs opnieuw moet implementeren in plaats van het Java Collections-framework te gebruiken, omdat ik de extra garbagecollection niet kan betalen. Dat kan opduiken als ik kijk naar prioriteit wachtrijen.

Ik heb ook problemen met het weergeven van scores en voortgang met Canvas.drawText. Dit is slecht,

canvas.drawText("Your score is: " + Score.points, x, y, paint);

omdat Strings, char arrays en StringBuffers wordt helemaal toegewezen om het te laten werken. Als u een paar tekstweergave-items hebt en het frame 60 keer per seconde uitvoert, dat begint op te tellen en uw hiaat voor het verzamelen van garanties vergroot. Ik denk dat de beste keuze hier is om te houden char[] matrices en decodeer je int of double handmatig erin en aaneenschakelingen aaneenkoppelen aan het begin en einde. Ik zou graag willen horen of er iets schoner is.

Ik weet dat er anderen zijn die hiermee te maken hebben. Hoe ga je ermee om en wat zijn de valkuilen en best practices die je hebt ontdekt om interactief op Java of Android te draaien? Deze GS-problemen zijn genoeg om me manueel geheugenbeheer te laten missen, maar niet erg veel.


62
2018-03-20 17:51


oorsprong


antwoorden:


Ik heb gewerkt aan mobiele Java-spellen ... De beste manier om GC'ing-objecten te vermijden (die op hun beurt zal activeer de GC op een of ander moment en zal de prestaties van je spel doden) is simpelweg om te voorkomen dat je ze in je hoofdgame maakt.

Er is geen "schone" manier om hiermee om te gaan en ik zal eerst een voorbeeld geven ...

Meestal heb je bijvoorbeeld 4 ballen op het scherm op (50,25), (70,32), (16,18), (98,73). Welnu, hier is uw abstractie (vereenvoudigd omwille van dit voorbeeld):

n = 4;
int[] { 50, 25, 70, 32, 16, 18, 98, 73 }

Je "knalt" de 2e bal die verdwijnt, je int [] wordt:

n = 3
int[] { 50, 25, 98, 73, 16, 18, 98, 73 }

(let op hoe we ons niet eens bekommeren om het "schoonmaken" van de 4e bal (98,73), we houden eenvoudig bij hoeveel ballen we nog over hebben).

Handmatig objecten volgen, helaas. Dit is hoe het wordt gedaan op de meeste huidige goed presterende Java-games die op mobiele apparaten worden uitgebracht.

Nu voor snaren, hier is wat ik zou doen:

  • bij het initialiseren van het spel, gebruik het voorvoegsel DrawText (...)  slechts één keer de getallen 0 tot 9 die u opslaat in a BufferedImage[10] matrix.
  • bij het initialiseren van het spel, maak één keer een proefrit "Je score is: "
  • als het "Je score is: "moet echt opnieuw getekend worden (omdat het bijvoorbeeld transparant is), dan opnieuw tekenen van uw vooraf opgeslagen BufferedImage
  • lus om de cijfers van de score te berekenen en toe te voegen, na de "Je score is: ", elk cijfer handmatig één voor één (door elke keer dat het corresponderende cijfer (0 tot 9) van uw te kopiëren BufferedImage[10] waar je ze hebt opgeslagen.

Dit geeft je het beste van twee werelden: je krijgt het hergebruik van de DrawText (...) lettertype en je creëerde precies nul objecten tijdens je hoofdlus (omdat jij ook ontweken de oproep aan DrawText (...) die zelf mei heel goed crappily producerend, goed, onnodige crap).

Nog een "voordeel" hiervan "nul-object creatie gelijkspel score" is dat zorgvuldige beeldcaching en hergebruik voor de lettertypen niet echt "handmatige objecttoewijzing / deallocatie", het is echt voorzichtig caching.

Het is niet "schoon", het is geen "goede oefening", maar zo wordt het gedaan in de beste mobiele games (zoals bijvoorbeeld Uniwar).

En het is snel. Verdorie snel. Sneller dan iets waarbij het creëren van objecten wordt betrokken.

PS: als je zorgvuldig naar een paar mobiele games kijkt, merk je dat vaak lettertypen feitelijk geen systeem- / Java-lettertypen zijn, maar pixel-perfecte lettertypen die speciaal voor elke game zijn gemaakt (hier heb ik je een voorbeeld gegeven van hoe je het cachesysteem moet gebruiken / Java-lettertype, maar je zou natuurlijk ook een pixel-perfect / bitmapped lettertype kunnen cachen / hergebruiken).


55
2018-03-20 18:55



Hoewel dit een 2 jaar oude vraag is ...

De enige en de beste aanpak om GC-lagers te vermijden, is om GC zelf te vermijden door alle vereiste objecten enigszins statisch toe te wijzen (inclusief bij het opstarten). Maak vooraf het vereiste object en laat ze nooit verwijderen. Gebruik object pooling om bestaande objecten opnieuw te gebruiken.

Hoe dan ook, je hebt misschien een pauze, zelfs nadat je alle mogelijke optimalisaties op je code hebt uitgevoerd. Omdat iets anders dan uw app-code maakt nog steeds intern GC-objecten aan wat uiteindelijk afval zal worden. Bijvoorbeeld, Java-basisbibliotheek. Zelfs met behulp van eenvoudig List klasse mei maak garbages. (dus moet vermeden worden) Het aanroepen van een van de Java API kan garbages creëren. En deze toewijzingen zijn niet te voorkomen als u Java gebruikt.

Omdat Java is ontworpen om GC te gebruiken, zult u problemen ondervinden door een gebrek aan functies als u GC echt probeert te vermijden. (zelfs List klasse moet worden vermeden) Omdat het toestaat de GC, alle bibliotheken mei gebruik GC, dus jij virtueel / praktisch geen bibliotheek. Ik vind het vermijden van GC op GC-gebaseerde taal een soort gekke proef.

Uiteindelijk is de enige praktische manier om naar een lager niveau te gaan waar je het geheugen volledig zelf kunt bedienen. Zoals C-familietalen (C, C ++, etc.). Ga dus naar NDK.

Notitie

Google verzendt nu incrementele (concurrent?) GC die veel kan pauzeren. In elk geval betekent een incrementele GC dat de GC-belasting in de loop van de tijd wordt verdeeld, zodat u de uiteindelijke pauze nog steeds ziet als de distributie niet ideaal is. Ook de GC-prestaties zelf zullen afnemen als gevolg van het neveneffect van minder overheadkosten voor batchverwerking en distributie.


15
2017-07-09 21:20



Ik heb mijn eigen afvalloze versie van gebouwd String.format, tenminste soort van. Je vindt het hier: http://pastebin.com/s6ZKa3mJ (excuseer de Duitse opmerkingen).

Gebruik het als volgt:

GFStringBuilder.format("Your score is: % and your name is %").eat(score).eat(name).result

Alles is geschreven in een char[] matrix. Ik moest de omzetting van integer naar string handmatig uitvoeren (cijfer voor cijfer) om alle rommel kwijt te raken.

Afgezien daarvan gebruik ik SparseArray waar mogelijk, omdat alle Java-datastructuren zoals HashMap, ArrayList enz. moeten boksen gebruiken om met primitieve typen om te gaan. Elke keer dat je een doos inpakt int naar Integer, deze Integer object moet worden opgeschoond door de GC.


7
2017-11-13 20:34



Als u de tekst niet vooraf wilt renderen zoals is voorgesteld, drawText accepteert een CharSequence wat betekent dat we onze eigen slimme implementatie ervan kunnen maken:

final class PrefixedInt implements CharSequence {

    private final int prefixLen;
    private final StringBuilder buf;
    private int value; 

    public PrefixedInt(String prefix) {
        this.prefixLen = prefix.length();
        this.buf = new StringBuilder(prefix);
    }

    private boolean hasValue(){
        return buf.length() > prefixLen;
    }

    public void setValue(int value){
        if (hasValue() && this.value == value) 
            return; // no change
        this.value = value;
        buf.setLength(prefixLen);
        buf.append(value);
    }


    // TODO: Implement all CharSequence methods (including 
    // toString() for prudence) by delegating to buf 

}

// Usage:
private final PrefixedInt scoreText = new PrefixedInt("Your score is: ");
...
scoreText.setValue(Score.points);
canvas.drawText(scoreText, 0, scoreText.length(), x, y, paint);

Het tekenen van de score veroorzaakt geen toewijzingen (behalve misschien een of twee keer in het begin wanneer bufs interne array moet misschien worden gekweekt, en wat dan ook drawText is aan het).


4
2018-03-20 19:50



In een situatie waarin het van cruciaal belang is om GC-pauzes te vermijden, kunt u een truc gebruiken om GC opzettelijk te activeren op een moment dat u weet dat de pauze er niet toe doet. Bijvoorbeeld, als de afvalintensieve "showScores" -functie wordt gebruikt aan het einde van een spel, zal de gebruiker niet overdreven afgeleid worden door een extra 200ms vertraging tussen het tonen van het scorescherm en het starten van de volgende game ... zodat je zou kunnen bellen System.gc() zodra het scorescherm is geverfd.

Maar als je deze truc gebruikt, moet je voorzichtig zijn om het alleen te doen op punten waar de GC-pauze niet irritant is. En doe het niet als u zich zorgen maakt over het leegmaken van de batterij van de handset.

En doe het niet in multi-user of niet-interactieve applicaties, omdat je waarschijnlijk de applicatie hierdoor over het algemeen langzamer maakt.


3
2018-03-21 00:40



Met betrekking tot de iterator-toewijzing is het eenvoudig om iterators op ArrayList `s te vermijden. In plaats van

for(GameObject gob : interactiveObjects)
    gob.onDraw(canvas);

je kunt het gewoon doen

for (int i = 0; i < interactiveObjects.size(); i++) {
    interactiveObjects.get(i).onDraw();
}

2
2017-07-09 22:42