Tuesday, August 04, 2009

Try Again ...

La bonne nouvelle, c'est que j'ai enfin un mécanisme me permettant de recharger un niveau quand Bilou s'est pris trop de coup. La mauvaise, c'est qu'à recommencer 20 fois le même niveau, je finis par mettre en évidence des fuites de mémoire dans mon code.
Il est temps de reprendre le code de mon prototype de manière un peu plus rigoureuse, de passer en revue mes classes en suivant les bons conseils++ d'Axel. Jusqu'à 300K qui partent en fumée à chaque vie, ça coûte vite cher sur une machine qui n'a que 4MB.

Good news first: I can at last reload a level when Bilou has been hit too much. The downside is, that this highlight a memory leak in my game engine. Most likely some machine state isn't properly reclaimed. It's time to print my code and read it back with a mop in hand ...

  • double invocation of GameScript ctor on reload : >280 K goes away
  • not taking care of transitions when cleaning the state machine : 5K
  • not taking care of cleaning "guns" : neglectible right now
  • other (under inspection) lost things : 5K
  • not taking care of OAMs allocation over iterations : invisible sprites after ~16 attempts
edit: fixing a stupid and obvious bug reduce the leak from 300K per live to 10K per live. Below comes the checkram() function i'm using to count available memory (that simply allocate as much as possible, then releases everything). Just refrain yourself from using std::whatever to store allocated chunks if you don't want to see a bad_alloc exception thrown in the middle of the process.

int checkram() {
int total=0;
iprintf("grabbing ...");
void **chunks=0;
void **chunk=0, **prev=0;

for (int chunksize = 1024*1024; chunksize>32; chunksize=chunksize/2) {
int n=0;
while((chunk=(void**)malloc(chunksize))) {
*chunk=(void*) prev;
chunks=chunk;
n++;
total+=chunksize;
prev=chunk;
}
iprintf("%i,",n);
}

iprintf("releasing ...");
while(chunks) {
void **chunk=chunks;
chunks=(void**) *chunk;
free(chunk);
}
return total;
}
Bien sûr, une telle fonction est une dépense de resources matérielles inutile si l'on connaît suffisamment l'implémentation de malloc dans l'environnement précis où l'on développe. En l'occurence, ludo connaît une méthode bien plus efficace et élégante pour libnds. And ludo's sources come from this gbadev post

4 comments:

Ludo6431 said...

Salut,
au début je faisais ça aussi pour récupérer la mémoire libre mais je trouvais ça pas très élégant, j'ai un peu fouiller le net et j'ai trouver du code que j'ai posté sur mon site :
http://sites.google.com/site/ludo6431/nds-tips-tricks/get-free-memory

PypeBros said...

très sympa d'avoir ouvert un site pour réagir à ce billet! Et rien de surprenant à ce que malloc ait une "granularité" de 16 bytes: il lui faut ...

mais je raconterai ça dans un prochain post.

Ludo6431 said...

[blabla]Il fallait que je l'ouvre ce site dans tout les cas ^^ (mon hébergeur actuel n'est pas top http://ludo6431.o-n.fr (down en ce moment d'ailleurs)).[/blabla]

Et pour malloc, je suppose que c'est le même principe que les clusters sur un FS. isn't it ?

PypeBros said...

en fait, pas vraiment. Dans un système de fichiers, tu construis des clusters (en groupant n secteurs) de manière à n'avoir que 2^k clusters en tout, et donc pouvoir les adresser sur k bits.

En mémoire, tu sais toujours adresser tous tes bytes, par contre, tu dois retenir de l'information sur chaque zone libérée. Sa taille, l'emplacement de la zone libre suivante au minimum, et selon l'algorithme utilisé, l'emplacement d'une zone 2x plus grande ou celui d'une zone 2x plus loin pour accélérer les opérations d'allocation/désallocations.

Résultat, tu te retrouves avec une structure décrivant une zone libre qui fait au moins n bytes, et il vaut mieux éviter de renvoyer une zone de moins de n bytes au "logiciel client" sous peine de se retrouver dans la situation embarassante de ne plus pouvoir mémoriser qu'une zone spécifique est vide.

Si tu as souvent besoin de petites zones (moins de 16 bytes qui est une valeur typique des implémentations que je connais), il vaut alors mieux passer à un "allocateur de cellules" qui prend p.ex. 4KB et les divise en 1000x4 bytes.