web-dev-qa-db-fra.com

Comment éviter les retards de récupération de place dans les jeux Java jeux? (Meilleures pratiques)

J'optimise les performances des 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 elle peut atteindre 200 ms sur des appareils très lents.

J'utilise le profileur ddms (qui fait partie du SDK Android SDK) pour rechercher d'où viennent mes allocations de mémoire et les supprimer de mon dessin interne et de mes boucles logiques.

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

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

où chaque fois que la boucle était exécutée, un iterator était 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 être prudent ou même les réimplémenter au lieu d'utiliser le cadre de collections Java Collections car je ne peux pas me permettre le ramasse-miettes supplémentaire. Cela peut apparaître lorsque je regarde les files d'attente prioritaires.

J'ai également du mal à afficher les scores et les progrès à l'aide de Canvas.drawText. C'est mauvais,

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

car Strings, char 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, cela commence à s'additionner et augmentera vos hoquets de récupération de place. Je pense que le meilleur choix ici est de garder char[] crée et décode manuellement votre int ou double manuellement et concatène les chaînes au début et à la fin. J'aimerais savoir s'il y a quelque chose de plus propre.

Je sais qu'il doit y en avoir d'autres là-bas. Comment gérez-vous cela et quels sont les pièges et les meilleures pratiques que vous avez découvert pour fonctionner 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 très beaucoup.

62
Brian

J'ai travaillé sur Java jeux mobiles ... 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 performances de votre jeu) est tout simplement pour é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 ...

En règle générale, vous avez, disons, 4 balles à l'écran à (50,25), (70,32), (16,18), (98,73). Eh bien, voici votre abstraction (simplifiée pour cet exemple):

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

Vous "pop" la 2ème balle 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 billes qu'il nous reste).

Suivi manuel des objets, malheureusement. Voici comment cela se fait sur la plupart des jeux Java Java performants actuellement disponibles sur les appareils mobiles).

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

  • à l'initialisation du jeu, pré-dessiner en utilisant drawText (...)ne seule fois les chiffres 0 à 9 que vous enregistrez dans un BufferedImage[10] tableau.
  • à l'initialisation du jeu, pré-dessiner une fois "Votre score est:"
  • si le "Votre score est:" doit vraiment être redessiné (parce que, par exemple, il est transparent), puis redessinez-le à partir de votre BufferedImage pré-stocké
  • boucle pour calculer les chiffres du score 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 de la police drawtext (...) et vous avez créé exactement zéro objet pendant votre boucle principale (parce que vous aussi esquivé le appel à drawtext (...) qui lui-même peut très bien être généré de façon insensée, enfin, merde inutile).

Un autre "avantage" de cela "score nul de création d'objet" est que la mise en cache et la réutilisation soigneuses des images ne sont pas vraiment "allocation/désallocation manuelle d'objet", c'est vraiment mise en cache juste prudente.

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

Et c'est rapide. Très vite. Plus rapide que n'importe quoi impliquant la création d'un objet.

PS: en fait, si vous regardez attentivement quelques jeux mobiles, vous remarquerez que souvent les polices ne sont en fait pas des polices système/Java mais des polices parfaites pour les pixels conçues spécifiquement pour chaque jeu (ici, je viens de vous donner un exemple de comment mettre en cache la police système/Java, mais vous pouvez également mettre en cache/réutiliser une police pixel-perfect/bitmap).

56
SyntaxT3rr0r

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

La seule et la meilleure approche pour éviter le décalage du GC est d'éviter le GC lui-même en allouant tous les objets requis de manière quelque peu statique (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.

Quoi qu'il en soit, vous pouvez avoir une pause, même après avoir effectué toutes les optimisations possibles sur votre code. Parce que autre chose que le code de votre application crée toujours des objets GC en interne qui finira par devenir une ordure. Par exemple, Bibliothèque de base Java . Même l'utilisation d'une simple classe List peut créer des déchets. (à éviter donc) L'appel à l'un des Java API peut créer des déchets. Et ces allocations ne sont pas évitables lorsque vous utilisez Java.

De plus, comme 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) Parce qu'elle autorise le GC, toutes les bibliothèques peuvent utiliser GC, donc vous virtuellement/pratiquement pas de bibliothèque . Je considère qu'éviter GC sur un langage basé sur GC est une sorte d'épreuve démente.

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.). Allez donc à NDK.

Remarque

Maintenant, Google envoie des GC incrémentiels (simultanés?), Ce qui peut réduire considérablement la pause. Quoi qu'il en soit, le GC incrémentiel 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. De plus, les performances du GC lui-même seront réduites en raison des effets secondaires d'une réduction des frais généraux des opérations de traitement par lots et de distribution.

15
Eonil

J'ai créé ma propre version sans ordures 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 char[] tableau. J'ai dû implémenter la conversion de l'entier en chaîne manuellement (chiffre par chiffre) pour se débarrasser de tous les déchets.

En dehors de cela, j'utilise SparseArray dans la mesure du possible, car toutes les structures de données Java telles que HashMap, ArrayList etc. doivent utiliser boxing afin de traiter les types primitifs. Chaque fois que vous boxez un int à Integer, cet objet Integer doit être nettoyé par le GC.

7
David Scherfgen

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

Désormais, le dessin du score ne provoque aucune allocation (sauf peut-être une ou deux fois au début lorsque le tableau interne de buf doit être agrandi, et ce que drawText peut faire).

4
gustafc

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 "showScores" intensive en ordures 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 prochaine partie ... vous pouvez donc appeler System.gc() une fois l'écran des partitions peint.

Mais si vous recourez à cette astuce, vous devez faire attention à ne le faire qu'aux points où la pause du GC ne sera pas gênante. Et ne le faites pas si vous avez peur de décharger la batterie du combiné.

Et ne le faites pas dans des applications multi-utilisateurs ou non interactives, car vous ferez très probablement ralentir globalement l'application en faisant cela.

3
Stephen C

Concernant l'allocation des itérateurs, il est facile d'éviter les itérateurs sur ArrayList. Au lieu de

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

tu peux juste faire

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