2 votes

Gestion de la mémoire et performances

Je suis en train de créer un jeu WebGL et j'ai tellement avancé que j'ai commencé à chercher des goulots d'étranglement au niveau des performances. Je peux voir qu'il y a beaucoup de petites baisses de FPS quand il y a des GC en cours. J'ai donc créé un petit gestionnaire de pool de mémoire. Je vois encore beaucoup de GC après avoir commencé à l'utiliser et je soupçonne que j'ai quelque chose de mal.

Le code de mon pool de mémoire ressemble à ceci :

function Memory(Class) {
    this.Class = Class;
    this.pool = [];

  Memory.prototype.size = function() {
    return this.pool.length;
  };

  Memory.prototype.allocate = function() {
    if (this.pool.length === 0) {
        var x = new this.Class();
        if(typeof(x) == "object") {
            x.size = 0;
            x.push = function(v) { this[this.size++] = v; };
            x.pop = function() { return this[--this.size]; };
        }
        return x;
    } else {
      return this.pool.pop();
    }
  };

  Memory.prototype.free = function(object) {
      if(typeof(object) == "object") {
          object.size = 0;
      }
      this.pool.push(object);
  };

  Memory.prototype.gc = function() {
    this.pool = [];
  };
}

J'utilise ensuite cette classe comme ceci :

game.mInt = new Memory(Number);
game.mArray = new Memory(Array); // this will have a new push() and size property.
 // Allocate an number
 var x = game.mInt.allocate();

 <do something with it, for loop etc>

 // Free variable and push into mInt pool to be reused.
 game.mInt.free(x);

Ma gestion de la mémoire pour un tableau est basée sur l'utilisation de myArray.size au lieu de length, ce qui permet de garder la trace de la taille actuelle réelle du tableau dans un tableau surdimensionné (qui a été réutilisé).

Donc, pour ma question actuelle :

Cette approche permet d'éviter la GC et de conserver la mémoire pendant le jeu. Les variables que je déclare avec "var" à l'intérieur des fonctions seront-elles toujours GC même si elles sont retournées en tant que new Class() depuis ma fonction Mémoire ?

Ejemplo:

var x = game.mInt.allocate();
for(x = 0; x < 100; x++) {
   ...
}
x = game.mInt.free(x);

Est-ce que cela entraînera toujours la collecte de la mémoire de la "var" en raison d'une copie de la mémoire dans les coulisses ? (ce qui rendrait mon gestionnaire de mémoire inutile).

Mon approche est-elle bonne/pertinente dans mon cas avec un jeu pour lequel j'essaie d'obtenir des FPS élevées ?

2voto

Thomas Points 4525

Donc vous laissez JS instancier un nouvel objet

var x = new this.Class();

puis d'ajouter des méthodes anonymes à cet objet et donc d'en faire un objet unique.

x.push = function...
x.pop = function...

de sorte que maintenant chaque endroit où vous utilisez cet objet est plus difficile à optimiser par le moteur JS, parce qu'ils ont maintenant des interfaces/classes cachées distinctes. (égal n'est pas identique)

En outre, chaque fois que vous utiliserez ces objets, vous devrez mettre en œuvre des analyses de type supplémentaires, afin de convertir les objets de type Number Objet dans un primitif, et les typographies ne sont pas gratuites non plus. Par exemple, à chaque itération d'une boucle ? peut-être même plusieurs fois ?

Et toute cette surcharge juste pour stocker un flottant de 64 bits ?

game.mInt = new Memory(Number);

Et comme vous ne pouvez pas modifier l'état interne et donc la valeur d'un fichier de type Number ces valeurs sont fondamentalement statiques, comme leur homologue primitif.

TL;DR :

  • Ne mettez pas en commun les types de natifs, surtout pas les primitives . De nos jours, JS est plutôt bon pour optimiser le code s'il n'a pas à faire face à des surprises. Des surprises comme des objets distincts avec des interfaces distinctes qui doivent d'abord être convertis en une valeur primitive, avant de pouvoir être utilisés.

  • Le redimensionnement des tableaux n'est pas gratuit non plus. Bien que JS optimise cette opération et pré-alloue généralement plus de mémoire que le tableau n'en a besoin, il se peut que vous atteigniez cette limite, ce qui oblige le moteur à allouer une nouvelle mémoire, à déplacer toutes les valeurs dans cette nouvelle mémoire et à libérer l'ancienne.
    J'utilise généralement des listes de liens pour les pools.

  • N'essayez pas de tout mettre en commun. Réfléchissez aux objets qui peuvent réellement être réutilisés et à ce que vous faites pour les intégrer dans ce récit de "réutilisabilité".
    Je dirais : Si vous devez faire aussi peu que d'ajouter une seule nouvelle propriété à un objet (après sa construction) et donc vous devez delete cette propriété pour le nettoyage, cet objet doit no être mis en commun.

  • Classes cachées : Lorsque l'on parle d'optimisations en JS, il faut connaître ce sujet au moins à un niveau très basique
    résumé :

    • n'ajoutez pas de nouvelles propriétés après la construction d'un objet.
    • et pour prolonger ce premier point, aucun delete s !
    • l'ordre dans lequel vous ajoutez les propriétés est important
    • changer la valeur d'une propriété (même son type) n'a pas d'importance ! Sauf lorsque nous parlons de propriétés qui contiennent des fonctions (aka. méthodes). L'optimiseur peut être un peu pointilleux ici, lorsque nous parlons de fonctions attachées à des objets, alors évitez-le.
  • Et le dernier mais non le moindre : Distinguez les objets optimisés des objets "dictionnaires". D'abord dans vos concepts, puis dans votre code.
    Il n'y a aucun avantage à essayer de tout faire rentrer dans un modèle avec des interfaces statiques (on est en JS, pas en Java). Mais les types statiques facilitent la vie de l'optimiseur. Donc, composez les deux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X