Comment puis-je éviter les retards de collecte des ordures dans les jeux Java? (Meilleures Pratiques) [fermé]


Je suis performance tuning jeux interactifs en Java pour la plate-forme Android. De temps en temps, il y a un hoquet dans le dessin et l'interaction pour la collecte des ordures. Habituellement, c'est moins d'un dixième de seconde, mais parfois il peut être aussi grand que 200ms sur très lent appareils.

J'utilise le profileur ddms (partie du SDK Android) pour rechercher d'où viennent mes allocations de mémoire et les exciser de mes boucles de dessin et de logique internes.

Le pire délinquant avait été boucles courtes faites comme,

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

Où chaque fois que la boucle a été exécutée, il y avait un iterator alloué. J'utilise des tableaux (ArrayList) pour mes objets maintenant. Si jamais je veux des arbres ou des hachages dans une boucle interne, je sais que je dois faire attention ou même les réimplémenter au lieu d'utiliser le framework Java Collections car je ne peux pas me permettre la collecte de déchets supplémentaire. Cela peut apparaître lorsque je regarde les files d'attente prioritaires.

J'ai aussi du mal où je veux afficher les scores et progression en utilisant Canvas.drawText. C'est mauvais,

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

Parce que Strings, char les tableaux et StringBuffers seront alloués partout pour le faire fonctionner. Si vous avez quelques éléments d'affichage de texte et exécutez le cadre 60 fois par seconde qui commence à s'additionner et augmentera votre hoquet de collecte des ordures. Je pense que le meilleur choix ici est de conserver des tableaux char[] et de décoder manuellement vos int ou double et de concaténer des chaînes au début et à la fin. J'aimerais savoir s'il y a quelque chose nettoyeur.

Je sais qu'il doit y en avoir d'autres qui s'occupent de ça. Comment le gérez-vous et quels sont les pièges et les meilleures pratiques que vous avez découverts pour exécuter de manière interactive sur Java ou Android? Ces problèmes de gc sont suffisants pour me faire manquer la gestion manuelle de la mémoire, mais pas beaucoup.

Author: Gabe, 2010-03-20

6 answers

J'ai travaillé sur des jeux mobiles Java... La meilleure façon d'éviter les objets GC'ing (qui à leur tour doit déclencher le GC à un moment ou à un autre et doit tuer les perfs de votre jeu) est simplement d'éviter de les créer dans votre boucle de jeu principale en premier lieu.

Il n'y a pas de moyen "propre" de gérer cela et je vais d'abord donner un exemple...

Typiquement, vous avez, disons, 4 boules à l'écran à (50,25), (70,32), (16,18), (98,73). Eh bien, voici votre abstraction (simplifiée pour le bien de cet exemple):

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

Vous "pop" la 2ème boule qui disparaît, votre int [] devient:

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

(remarquez que nous ne nous soucions même pas de "nettoyer" la 4ème balle (98,73), nous gardons simplement une trace du nombre de balles qu'il nous reste).

Suivi manuel des objets, malheureusement. C'est ainsi que cela se fait sur la plupart des jeux Java performants actuels qui sont disponibles sur les appareils mobiles.

Maintenant pour les chaînes, voici ce que je ferais:

  • à l'initialisation du jeu, predraw utilisation de drawText(...) une seule fois les nombres de 0 à 9 que vous enregistrez dans un BufferedImage[10] tableau.
  • à l'initialisation du jeu, predraw une fois "Votre score est:"
  • si le "Votre score est:" doit vraiment être redessiné (parce que, disons, il est transparent), alors redessinez-le à partir de votre {[3 pré-stocké]}
  • boucle pour calculer les chiffres de la partition et ajouter, après le "Votre score est: ", chaque chiffre manuellement un par un (en copiant à chaque fois le chiffre correspondant (0 à 9) de votre BufferedImage[10] où vous les avez pré-stockés.

Cela vous donne le meilleur des deux mondes: vous obtenez la réutilisation du drawtext(...) font et vous avez créé exactement zéro objet pendant votre boucle principale (parce que vous avez également esquivé l'appel à drawtext(...) qui lui-mêmepeut très bien générer de la merde, eh bien, de la merde inutile).

Un autre "avantage" de ce "score nul de tirage de création d'objet" est cette image soignée la mise en cache et la réutilisation des polices ne sont pas vraiment "allocation/désallocation manuelle d'objets", c'est vraiment juste une mise en cache prudente.

Ce n'est pas "propre", ce n'est pas "une bonne pratique" mais c'est comme ça que ça se fait dans les jeux mobiles haut de gamme (comme, disons, Uniwar).

Et c'est rapide. Sacrément rapide. Plus rapide que tout ce qui implique la création d'objet.

PS: En fait, si vous regardez attentivement quelques jeux mobiles, vous remarquerez que souvent les polices ne sont pas en fait system / Java polices mais polices pixel-perfect faites spécifiquement pour chaque jeu (ici, je viens de vous donner un exemple de la façon de mettre en cache la police système/Java mais évidemment vous pouvez également mettre en cache/réutiliser une police pixel-perfect/bitmapped).

 55
Author: SyntaxT3rr0r, 2010-03-20 19:01:40

Bien que ce soit une question de 2 ans...

La seule et la meilleure approche pour éviter le décalage GC est d'éviter GC lui-même en allouant tous les objets requis quelque peu statiquement (y compris au démarrage). Pré-créez tous les objets requis et ne les supprimez jamais. Utilisez le regroupement d'objets pour réutiliser un objet existant.

De toute façon, vous pouvez avoir une pause éventuelle même après avoir fait toutes les optimisations possibles sur votre code. Parce que autre chose que le code de votre application est toujours en train de créer Objets GC en interne qui deviendront éventuellement des ordures. Par exemple, Bibliothèque de base Java. Même l'utilisation de la simple List classpeut créer des vêtements. (il faut donc éviter) Appeler l'une des API Java peut créer des garbages. Et ces allocations ne sont pas évitables lorsque vous utilisez Java.

Aussi, parce que Java est conçu pour utiliser GC, vous aurez des problèmes par manque de fonctionnalités si vous essayez vraiment d'éviter GC. (même la classe List doit être évitée) Car elle permet de la GC, toutes les bibliothèques peut utiliser GC, de sorte que vous pratiquement/presque pas de bibliothèque. Je considère qu'éviter GC sur le langage basé sur GC est une sorte d'essai fou.

En fin de compte, le seul moyen pratique est de descendre au niveau inférieur où vous pouvez contrôler complètement la mémoire vous-même. Tels que les langages de la famille C (C, C++, etc.). Alors allez à NDK.

Note

Maintenant, Google expédie incrémental (concurrent?) GC qui peut diminuer pause beaucoup. Quoi qu'il en soit, le GC incrémental signifie simplement distribuer la charge du GC au fil du temps, de sorte que vous voyez toujours une pause éventuelle si la distribution n'est pas idéale. En outre, les performances du GC elles-mêmes seront diminuées en raison de l'effet secondaire d'une réduction des frais généraux d'opération de traitement par lots et de distribution.

 15
Author: Eonil, 2014-05-24 09:48:29

J'ai construit ma propre version sans déchets de String.format, du moins en quelque sorte. Vous le trouvez ici: http://pastebin.com/s6ZKa3mJ (veuillez excuser les commentaires allemands).

Utilisez-le comme ceci:

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

Tout est écrit dans un tableau char[]. J'ai dû implémenter la conversion de l'entier en chaîne manuellement (chiffre par chiffre) pour me débarrasser de toutes les ordures.

En dehors de cela, j'utilise SparseArray si possible, car toutes les structures de données Java telles que HashMap, ArrayList etc. utiliser boxe afin de faire face aux types primitifs. Chaque fois que vous boxez un int à Integer, cet objet Integer doit être nettoyé par le GC.

 7
Author: David Scherfgen, 2014-06-26 07:27:34

Si vous ne voulez pas pré-rendre le texte comme cela a été proposé, drawText accepte tout CharSequence ce qui signifie que nous pouvons en faire notre propre implémentation intelligente:

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

Maintenant, dessiner le score ne provoque aucune allocation (sauf peut-être une ou deux fois au début lorsque le tableau interne de bufpeut devoir être développé, et tout ce que drawText fait).

 4
Author: gustafc, 2010-03-20 19:50:39

Dans une situation où il est essentiel d'éviter les pauses GC, une astuce que vous pouvez utiliser est de déclencher délibérément GC à un point où vous savez que la pause n'a pas d'importance. Par exemple, si la fonction garbage intensive "showScores" est utilisée à la fin d'une partie, l'utilisateur ne sera pas trop distrait par un délai supplémentaire de 200 ms entre l'affichage de l'écran de score et le démarrage de la partie suivante ... vous pouvez donc appeler System.gc() une fois que l'écran scores a été peint.

Mais si vous avez recours à ceci astuce, vous devez faire attention à ne le faire qu'aux points où la pause du GC ne sera pas ennuyeuse. Et ne le faites pas si vous craignez de vider la batterie du combiné.

Et ne le faites pas dans des applications multi-utilisateurs ou non interactives, car vous rendrez probablement l'application plus lente en faisant cela.

 3
Author: Stephen C, 2010-03-21 00:40:26

En ce qui concerne l'allocation d'itérateurs, éviter les itérateurs sur ArrayList est facile. Au lieu de

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

Vous pouvez juste faire

for (int i = 0; i < interactiveObjects.size(); i++) {
    interactiveObjects.get(i).onDraw();
}
 2
Author: user1410657, 2013-07-09 22:42:17