Comment faire le profilage et le pool de mémoire par système?

8

J'ai été intéressé par le profilage et la conservation d'un pool de mémoire gérée pour chaque sous-système, afin de pouvoir obtenir des statistiques sur la quantité de mémoire utilisée dans quelque chose comme des sons ou des graphiques. Cependant, quel serait un design qui fonctionne pour cela? Je pensais à utiliser plusieurs allocateurs et à n'en utiliser qu'un par sous-système, cependant, cela entraînerait des variables globales pour mes allocateurs (ou du moins il me semble). Une autre approche que j'ai vue / a été suggérée consiste simplement à surcharger new et à passer un allocateur pour un paramètre.

J'avais une question similaire à propos de stackoverflow ici avec une prime, cependant, il semble que j'étais peut-être trop vague ou tout simplement il n'y a pas assez de personnes ayant des connaissances dans le sujet.

chadb
la source
Supprimé ma réponse. Je suis retourné et j'ai lu les articles et les pools utilisés pour suivre les allocations par système ne font pas partie de ces articles (même s'ils étaient basés sur lui) Si je peux retrouver ces articles spécifiques, je les lierai à la place! Désolé!
James
1
Jetez un œil à ce billet de blog de Jesus de Santos Garcia. Dans ce document, il discute du suivi de la mémoire par sous-système et de l'utilisation de plusieurs allocateurs pour diverses exigences de mémoire.
7
Ne soyez pas obsédé par les paradigmes théoriques. Si vous avez besoin de fonctionnalités et de données dans le monde, il n'y a rien de mal avec les globaux. Il existe déjà un tas de fonctions globales comme new / delete / malloc / free. Ajoutez simplement ce que vous avez à faire pour faire le travail.
Maik Semder

Réponses:

1

C'est définitivement une question qui peut sembler vague pour certains;)

Mais je pense que je sais d'où tu viens.

Vous avez un million de choix sur la façon dont vous pouvez choisir de mettre en œuvre cela. Certains de ces choix devraient s'articuler à la fois sur les plates-formes cibles et sur les objectifs globaux de conception. Ces considérations briseront tous les liens, jusqu'à ce que vous vous sentiez suffisamment à l'aise avec différents coûts de mise en œuvre suffisamment pour développer d'abord la conception à partir de la plate-forme et les préoccupations générales de conception. Donc jusque-là, voici quelques façons qui ne vous coûteront pas en termes de complexité (charge de gestion) ou de traitement des suppressions ou des changements si vous changez d'avis ...

Si l'objectif est de mesurer et d'allouer, avec la possibilité d'utiliser des pools, vous devez d'abord penser à l'ensemble minimum de code habitable pour commencer. Pour des raisons d'explication, si vous êtes partial pour les classes, vous pouvez créer une classe, une laisser qui représente un segment de mémoire, ou utiliser à la place un ensemble de fonctions qui prennent un handle ou un nom de segment de mémoire. Pour être honnête, c'est vraiment une question de sémantique. La prochaine décision est nouvelle ou malloc; Je n'aime pas malloc parce que souvent je traite avec des constructions de bas niveau et je sais que dans la plupart des implémentations, les nouveaux appels s'appellent malloc, et je n'ai pas à m'inquiéter de la complexité de surcharger les nouveaux, et de m'inquiéter de cela sur toutes les plateformes . Cependant, j'ai souvent construit des systèmes ou des composants autour de la surcharge ou du raccordement de nouveaux. Et bien sûr, le problème principal ou la différence, c'est que le «nouveau» doit connaître le type avant l'allocation, alors que 'malloc' s'en fiche et avec malloc vous résolvez un type après l'allocation. Tout ce détail est pour vous donner une idée et un contexte pour prendre des décisions de conception dans ces domaines :)

Je vais donc choisir classe et malloc, parce que c'est plus facile à expliquer ici, mais ça n'a vraiment pas d'importance à la fin. Les composants internes finiront par présenter peu de différence matérielle par rapport au reste de la conception globale.

Donc, dans cette hypothèse, je sais que (ou je vais supposer que) je peux me retrouver avec 7 à 8 instanciations de classe de sous-système réel et anticiper des centaines de milliers d'appels d'allocation et gratuits. Étant donné qu'une grande partie de ma curiosité et de ma réelle motivation dans tout cela concerne les tailles et le profil, je ne veux pas alourdir les performances de l'application. Pour commencer, je pourrais décider de laisser le tout ouvert et public jusqu'à ce que je l'ait cloué, pendant que je passe en revue l'implémentation dans le reste de l'application; une structure le fera bien. Le 's_' est de montrer quels vars sont clairement destinés aux statistiques.

struct Mem
{
  int s_allocs;
  int s_frees;
  int s_peak;
  int s_current;
  void* heap; // if you wanted to go into having real actual separate heaps, else ignore
  void* alloc(int size);
  void free(void* p);

  Mem() {memset(this,0,szieof(Mem));}  // want this to be inlined with the call site constructor (a design decision example)
}

class MySubSystem
{
   Mem mem;
   ....  you get the idea
}

C'est extrêmementléger sur de nombreux fronts, et peut-être un bon endroit pour commencer à étoffer où vous avez vraiment voulu aller avec cela. Et vous avez immédiatement un problème, comment savez-vous la taille de l'article libéré. (Ce serait un problème à résoudre pour presque toutes les approches.) Comme il s'agit d'un forum de jeu, vous pouvez envisager de doper les premiers octets avec la taille, ou bien vous devez soit envelopper ou vous souvenir d'une autre manière. La plupart des sensibilités des développeurs de jeux ne devraient pas être trop contre le dopage, et c'est l'exemple le plus simple, étant donné que j'ai déjà fait un mur de texte. Fondamentalement, cela se passe comme suit: vous ne voulez pas que l'on puisse aider à détruire l'alignement inhérent, vous voulez savoir, car presque gratuitement, si la taille est cohérente. Donc quelque chose d'aussi simple que "s_allocs ++; s_total + = taille; uint64 * p = (uint64 *) malloc / calloc (taille + = 8); * p = 0xDEADDAED00000000 | Taille; return p + 1; "où les allocations seront inférieures à 4 Go, et uint64 est tout ce que le compilateur pense qu'un int non signé 64 bits est, et où vous pouvez vérifier la valeur d'intégrité sur free.

C'est une façon de vous procurer le strict minimum à un coût minimal qui répond aux exigences. Cela fait pasadresser les classes d'allocation qui ont des fonctions virtuelles si celles-ci sont à portée de profilage ou de gestion, car vous ne pouvez pas anticiper la taille de l'environnement c ++ que vous utilisez pour celles sans surcharger ou accrocher de nouvelles, ou si vous comptez sur le constructeur dans l'un des façons étranges qui ne pouvaient pas être gérées par une autre fonction «init». Sinon, un stuct est une classe est une allocation arbitraire et est tout de même, lorsque vous transtypez. Si vous êtes amateur de nouveautés et avez besoin de la table virtuelle inhérente ou de la sémantique des constructeurs, alors vous devez en accrocher de nouvelles, mais c'est un tout autre animal, que vous devez vraiment étudier pour vous assurer que vous répondez aux nouveaux besoins et que vous devez signaler votre code est un nouveau traitement, à quel compartiment cela s'applique. Mais sinon, le concept ci-dessus est le même.

Plus important encore, cela devrait stimuler votre cerveau et, espérons-le, suivre ce dont vous avez besoin et quelles sont vos tolérances, maintenant que vous avez vu un peu plus derrière le rideau. Il n'y a pas d'assistant :)

Celess
la source
Si vous utilisiez des fonctions de modèle pour allouer et libérer, cela ne devrait pas être un problème pour déterminer la taille nécessaire à libérer (et à allouer).
API-Beast
1
Il s'agissait de montrer le problème sous-jacent afin de fournir une base pour choisir quelle abstraction que ce soit, et non de présenter une abstraction particulière. Tout a ses compromis. La connaissance de la taille est nécessaire non pas pour faire l'acte de libération, mais pour les statistiques.
Celess
1

Vous n'avez pas besoin d'implémenter quoi que ce soit dans votre jeu pour ces données. Des outils tels que Massif Valgrind peuvent extraire toutes les données nécessaires des symboles de débogage. Vous pouvez afficher les vidages de Massif dans Massif Visualizer .

API-Beast
la source
4
Je suppose que la plupart des jeux graphiques ne vont pas être développés sur un système qui exécute Valgrind, malheureusement.
Kylotan
1

Je recommande fortement de ne pas écrire votre propre allocateur de mémoire. Vous avez besoin d'une solution stable, fiable et testée, avec une bonne fonctionnalité de débogage comme la détection de la corruption et, surtout, avec des statistiques fiables. Ce n'est pas une tâche facile et comporte de nombreux pièges. Il en existe de grands et faciles à utiliser, par exemple:

Allocateur Doug Lea

Il vient avec le concept d'espaces mémoire, vous pouvez en utiliser un par sous-système. Il est hautement optimisé et vous fournit d'excellentes statistiques et informations sur l'exécution.

Maik Semder
la source