[UNDOCUMENTED] Lister les objets contenus dans l'auto-release-pool

BruBru Membre
Au détour des forums, on retrouve souvent la même question :
Comment savoir si mon objet est dans l'autoreleasePool ?

Une méthode de classe non-documentée (showPools de NSAutoreleasePool) permet de lister les objets dans l'autoreleasePool. Cette liste est envoyée dans le fichier log (le même fichier que NSLog, normalement stdout) :

<br />[NSAutoreleasePool showPools];<br />


Le format de la sortie dans le log est pour chaque objet listé :

[tt]nom-app[pid app]      id-objet  (classe-objet)[/tt]

.

Réponses

  • AliGatorAliGator Membre, Modérateur
    18:58 modifié #2
    Ah ben enfin, en voilà  une bonne nouvelle :D
    Pour débuguer, ça peut s'avérer trèèèès pratique, en particulier quand il y a un release de trop, souvent parce qu'on lui a nous-même envoyé un release alors qu'il était dans l'autoreleasePool.
    Maintenant reste à  savoir si en pratique c'est utilisable : c'est quoi "id-object" pour toi ? le dernier nom de variable utilisé pour pointer sur l'objet ? (si c'est un objet temporaire non affecté à  une variable, ça va être dur...) ? Si c'est juste l'adresse mémoire où est stocké l'objet, ça va pas forcément aider des masses (quoiqu'en recoupant les données... mais bon quand même)...

    Enfin faut que je teste ça un de ces 4 ;)
  • Philippe49Philippe49 Membre
    18:58 modifié #3

    Super nouvelle que je n'avais pas encore lue. Cette méthode est déclarée dans une catégorie de NSAutoreleasePool, qi contient d'autres richesses :

    @interface NSAutoreleasePool (NSAutoreleasePoolDebugging)

    + (void)enableRelease:(BOOL)enable;

    + (void)showPools;

    + (void)resetTotalAutoreleasedObjects;

    + (unsigned)totalAutoreleasedObjects;

    + (void)enableFreedObjectCheck:(BOOL)enable;

    + (unsigned int)autoreleasedObjectCount;

    + (unsigned int)topAutoreleasePoolCount;

    + (unsigned int)poolCountHighWaterMark;
    + (void)setPoolCountHighWaterMark:(unsigned int)count;

    + (unsigned int)poolCountHighWaterResolution;

    + (void)setPoolCountHighWaterResolution:(unsigned int)res;

    @end

    Définitions et commentaires se trouvent dans le fichier d'en-têtes  : NSDebug.h
  • 18:58 modifié #4
    Il ne manque plus qu'un moyen pour savoir quand un objet est ajouté dans un autorelease pool et ça sera parfait (pour moi en tous cas) :)
  • Philippe49Philippe49 Membre
    18:58 modifié #5
    Un genre de notification lancée par le pool ... 
  • schlumschlum Membre
    février 2007 modifié #6
    dans 1169421640:

    + (void)resetTotalAutoreleasedObjects;

    Ca vide le pool ?  :o
    Si oui, super, car pour l'instant, le seul moyen que j'avais trouvé c'était de le récupérer par référence, de faire un "release" et d'allouer un nouveau pool...

    [Edit] Non, apparemment ça resette un compteur... Mais je ne perds pas espoir  :)

    [Edit2] J'ai trouvé une méthode non documentée "drain" qui devrait le faire, mais 10.4 only  :(

    Je trouve ça étonnant qu'il n'y ait pas de méthode simple pour vider un pool au cours d'un long thread de calcul pour ne pas surcharger la mémoire...  :crackboom:-
  • AliGatorAliGator Membre, Modérateur
    février 2007 modifié #7
    Ben pourquoi voudrais-tu un "reset" ou un "drain" ?
    Il suffit de créer des "autoreleasepools" aux endroits nécessaires, c'est toujours comme ça qu'on fait  ???

    • Pour les objets dont c'est toi qui gère la mémoire, il suffit de faire le moins d'autorelease possibles pour libérer la mémoire au moment le plus opportun plutôt que de tout laisser en automatique (au passage, c'est pour ça que je ne suis pas trop pour le système de garbage collector proposé en Obj-C 2.0, mieux vaut gérer la mémoire un maximum soi-même)
    • Pour le reste (variables autorelease que tu ne crées pas directement mais qui seraient créées par des fonctions internes aux frameworks Apple par exemple) ou tous les objets que tu serais obligé d'autoreleaser, créer un autoreleasepool en amont et le supprimer après.


    En particulier par exemple si tu as une boucle "while" assez longue et consommatrice de mémoire, toujours mettre une autoreleasepool à  l'intérieur de la boucle while, pour qu'à  chaque itération les objets autoreleasés soient purgés
    while( ... ) // grande boucle<br />{<br />&nbsp; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];<br /><br />&nbsp; // ... ton gros code dans la boucle, qui peut avoir plein de &quot;autorelease&quot;<br />&nbsp; // ... et créer plein d&#39;objets temporaires<br /><br />&nbsp; [pool release]; // on détruit la pool, et donc tous les objets qu&#39;il y a dedans<br />&nbsp; // donc tous ceux qui ont reçu un &quot;autorelease&quot; depuis la création de &#39;pool&#39;<br />}
    
    Car pour rappel un objet à  qui on envoie un message "autorelease" est automatiquement ajouté à  la dernière NSAutoreleasePool créée (celle au dessus de la pile des autoreleasepools).

    Pour un thread, c'est à  toi de créer une NSAutoreleasepool pour chaque thread que tu crées, mais tu peux créer plusieurs sous-autoreleasepools intermédiaires qui vont ponctuer ton code et libérer la mémoire au fur et à  mesure. En particulier si tu as des endroits dans ton thread où il y a une grosse consommation mémoire (création de pas mal d'images intermédiaires par exemple, prennant rapidement de la place mémoire) ;)
  • schlumschlum Membre
    18:58 modifié #8
    dans 1171274844:

    Ben pourquoi voudrais-tu un "reset" ou un "drain" ?
    Il suffit de créer des "autoreleasepools" aux endroits nécessaires, c'est toujours comme ça qu'on fait  ???

    Oui, c'est vrai...
    Mais ce serait quand même bien de pouvoir le vider, ça permettrait comme ça de le définir en dehors de la boucle et de simplement le vider à  la fin de chaque itération  :)


    [*]Pour les objets dont c'est toi qui gère la mémoire, il suffit de faire le moins d'autorelease possibles pour libérer la mémoire au moment le plus opportun plutôt que de tout laisser en automatique (au passage, c'est pour ça que je ne suis pas trop pour le système de garbage collector proposé en Obj-C 2.0, mieux vaut gérer la mémoire un maximum soi-même)

    Ca je fais déjà , et je suis entièrement d'accord avec toi
    Mais c'est pas toujours possible de maà®triser tous les objets sans AutoReleasePool... Surtout avec les retours de fonctions.

    Pour un thread, c'est à  toi de créer une NSAutoreleasepool pour chaque thread que tu crées, mais tu peux créer plusieurs sous-autoreleasepools intermédiaires qui vont ponctuer ton code et libérer la mémoire au fur et à  mesure. En particulier si tu as des endroits dans ton thread où il y a une grosse consommation mémoire (création de pas mal d'images intermédiaires par exemple, prennant rapidement de la place mémoire) ;)

    Effectivement, je vais faire comme ça...
    Mais je reste frustré de pas pouvoir vider le pool  ;D
  • AntilogAntilog Membre
    18:58 modifié #9
    Je suis totalement en accord avec Ali!  

    Autoreleaser au minimum
    Le GC va autoriser tout un tas de comportements laxistes.

    Le meilleur moyen de vider la piscine, c'est:
    [pool release];
  • AliGatorAliGator Membre, Modérateur
    février 2007 modifié #10
    dans 1171275959:

    Mais c'est pas toujours possible de maà®triser tous les objets sans AutoReleasePool... Surtout avec les retours de fonctions.
    Ben c'est pour ça que je te conseille de créer des pools intermédiaires quand tu en as besoin.

    Il m'est déjà  arrivé par exemple de créer une NSAutoreleasePool pour la releaser juste disons 10 lignes plus loin, tout ça pour encapsuler 3 ou 4 appels à  des fonctions du framework Cocoa (donc dont je ne maitrisais pas la gestion mémoire interne) qui traitaient des images (changement de taille, extraction d'une imageRep, etc) : au final en 3 ou 4 appels à  des fonctions Cocoa j'avais pas mal de mémoire allouée (autoreleasée) qui n'attendait que la destruction du dernier pool pour se libérer. D'où la création/destruction de mon autoreleasepool autour de ces lignes, même s'il n'y avait que 3 lignes.

    Si tu fais autrement ou que tu trouves qu'il y aurait besoin d'un "drain" c'est que tu n'as pas forcément une façon de programmer très Cocoa, le fonctionnement de la pile de pools étant tout à  fait adapté à  ce que tu veux faire, sans avoir besoin de "drain".
    D'ailleurs si tu veux mimiquer la fonction [tt][pool drain][/tt] c'est simple, il suffit de faire un [tt][pool release]; pool = [[NSAutoreleasePool alloc] init];[/tt] pour détruire ta pool et en recréer une toute neuve. Ca fait exactement la même chose.

    Sauf que le principe des AutoreleasePools c'est d'encadrer un bloc de code (celle par défaut encadrant le code à  l'intérieur de la runloop, elle est créée au début de chaque itération de la runloop et détruite à  la fin de chaque itération), pas de ponctuer le code de commandes "flush" (ou "drain") ;)
  • schlumschlum Membre
    18:58 modifié #11
    Oui, la création d'un AutoReleasePool dans une boucle convient... C'est juste que j'aurais aimé le garder à  l'extérieur  :P (vieux réflexe venant du C)
  • AliGatorAliGator Membre, Modérateur
    février 2007 modifié #12
    Ben heu l'extérieur ou l'intérieur c'est pas le problème. Si tu veux vider une autoreleasepool, tu la détruit, en lui envoyant un release. Elle se détruit et détruit tout les objets qu'elle contient. Point barre.

    Après si tu voulais mettre ton "flush imaginaire" à  l'extérieur de la boucle et non à  l'intérieur (je vois mal l'intérêt mais bon passons), ben tu peux très bien le faire avec les autoreleasepools, il suffit de créer ta pool avant le début de ta boucle et la supprimer à  la fin de ta boucle (là  où tu voulais mettre ton flush)

    Je vois pas l'intérêt de faire ça plutôt que de mettre ta pool à  l'intérieur de la boucle et ainsi libérer la mémoire à  chaque itération, mais bon. Ce que tu voulais faire avec ton [pool flush] il suffit de le faire avec [pool release] (en le balançant avec la création d'une pool avant), quel que soit l'endroit où tu voulais effectuer ton flush, intérieur ou extérieur de boucle
  • schlumschlum Membre
    février 2007 modifié #13
    dans 1171295202:

    Ben heu l'extérieur ou l'intérieur c'est pas le problème. Si tu veux vider une autoreleasepool, tu la détruit, en lui envoyant un release. Elle se détruit et détruit tout les objets qu'elle contient. Point barre.

    Après si tu voulais mettre ton "flush imaginaire" à  l'extérieur de la boucle et non à  l'intérieur (je vois mal l'intérêt mais bon passons), ben tu peux très bien le faire avec les autoreleasepools, il suffit de créer ta pool avant le début de ta boucle et la supprimer à  la fin de ta boucle (là  où tu voulais mettre ton flush)

    Je vois pas l'intérêt de faire ça plutôt que de mettre ta pool à  l'intérieur de la boucle et ainsi libérer la mémoire à  chaque itération, mais bon. Ce que tu voulais faire avec ton [pool flush] il suffit de le faire avec [pool release] (en le balançant avec la création d'une pool avant), quel que soit l'endroit où tu voulais effectuer ton flush, intérieur ou extérieur de boucle

    C'est juste qu'en général j'évite de déclarer des objets dans les boucles, rien de plus  ;)
    Donc je préférerais faire ça :
    NSAutoreleasePool *pool = [[NSAutoReleasePool alloc] init];<br />for(...;...;...) {<br />    // [...]<br />    [pool drain];<br />}<br />[pool release];
    


    Et puis ça permettrait aussi de le vider sous certaines conditions...

    Enfin bon, c'est pas un drame non plus hein, je m'en remets   :P
  • AliGatorAliGator Membre, Modérateur
    février 2007 modifié #14
    dans 1171308372:
    Donc je préférerais faire ça :
    NSAutoreleasePool *pool = [[NSAutoReleasePool alloc] init];<br />for(...;...;...) {<br />    // [...]<br />    [pool drain];<br />}<br />[pool release];
    

    Ben rien ne t'empêche de le faire, tu peux tout à  fait, regarde :
    NSAutoreleasePool *pool = [[NSAutoReleasePool alloc] init];<br />for(...;...;...) {<br />    // [...]<br />    [pool release]; pool = [NSAutoreleasePool new];<br />}<br />[pool release];
    
    Et voilà .  :p

    Ou sinon si c'est juste la déclaration de ta variable dans ta boucle, tu peux aussi séparer la déclaration (à  l'extérieur de la boucle) et l'affectation ([tt]pool = [NSAutoreleasePool new];[/tt] que tu ferais à  l'intérieur de la boucle)
  • ChachaChacha Membre
    18:58 modifié #15
    Oui mais Ali, lui ce qu'il veut c'est éviter le surcoût dû à  la destruction/création à  chaque tour de boucle !
    On me répondra "as-tu vraiment mesuré une pénalité ?" Je répondrai "non, bien sûr, mais je me fie à  mon instinct" ! Effectivement, on peut se demander quel est le coût de destruction/création d'un Autorelease pool avec le nombre d'appels de méthode que cela implique. Moi je n'en sais rien.
    Mais par exemple, en C++, pour optimiser, tu n'écrirais pas naturellement :
    <br />for(...)<br />{<br />&nbsp; vector&lt;int&gt; v;<br />&nbsp; ...v.push_back()....v.push_back()....<br />}<br />
    


    mais plutôt
    <br />vector&lt;int&gt; v;<br />for(...)<br />{<br />&nbsp; v.resize(0);<br />&nbsp; ...v.push_back()....v.push_back()....<br />}<br />
    

    Ainsi, ça évite de coûteuses (?) allocations mémoires.

    +
    Chacha
  • BruBru Membre
    18:58 modifié #16
    dans 1171323298:

    Oui mais Ali, lui ce qu'il veut c'est éviter le surcoût dû à  la destruction/création à  chaque tour de boucle !
    On me répondra "as-tu vraiment mesuré une pénalité ?" Je répondrai "non, bien sûr, mais je me fie à  mon instinct" ! Effectivement, on peut se demander quel est le coût de destruction/création d'un Autorelease pool avec le nombre d'appels de méthode que cela implique. Moi je n'en sais rien.
    Mais par exemple, en C++, pour optimiser, tu n'écrirais pas naturellement :
    [...]
    Ainsi, ça évite de coûteuses (?) allocations mémoires.


    Ouais... enfin si on commence à  comparer ce qui est incomparable (les langages entre eux), on n'a pas fini.

    NSAutoreleasePool est un des piliers fondamentaux du Foundation.
    Il est surexploité aussi bien par Apple dans ses frameworks que par les tiers.
    Aussi, je pense que NSAutoreleasePool est suffisamment "optimisé" pour faire son job.

    Maintenant, la conception d'une classe comme NSAutoreleasePool est ultra-simple. En 4 bouts de code, on peut réimplanter toutes les fonctionnalités de cette classe et y ajouter les siennes propres. Puis, avec un poseAs:, le tour est joué !

    .
  • schlumschlum Membre
    février 2007 modifié #17
    dans 1171322843:

    Ben rien ne t'empêche de le faire, tu peux tout à  fait, regarde :
    NSAutoreleasePool *pool = [[NSAutoReleasePool alloc] init];<br />for(...;...;...) {<br />    // [...]<br />    [pool release]; pool = [NSAutoreleasePool new];<br />}<br />[pool release];
    
    Et voilà .  :p


    dans 1171271341:

    Si oui, super, car pour l'instant, le seul moyen que j'avais trouvé c'était [...] de faire un "release" et d'allouer un nouveau pool...


    ;)

    dans 1171323298:

    Oui mais Ali, lui ce qu'il veut c'est éviter le surcoût dû à  la destruction/création à  chaque tour de boucle !


    Enfin comme je disais, c'est pas gravissime non plus... Juste que je trouve que ça aurait été judicieux et peu coûteux d'ajouter une fonction qui vide le pool  ;)
  • schlumschlum Membre
    18:58 modifié #18
    dans 1171354351:

    Maintenant, la conception d?une classe comme NSAutoreleasePool est ultra-simple. En 4 bouts de code, on peut réimplanter toutes les fonctionnalités de cette classe et y ajouter les siennes propres. Puis, avec un poseAs:, le tour est joué !

    Tiens, intéressant comme idée, j'avais jamais entendu parler de "poseAsClass:" encore...
Connectez-vous ou Inscrivez-vous pour répondre.