Page 1 sur 1

Libérer la mémoire.

Posté : 24 mars 2009, 15:56
par SpintroniK
Bonjour,

Je suis sur un script qui risque de demander pas mal de mémoire (tout en restant relativement raisonnable).
J'ai donc une classe qui doit parser un document et ensuite sortir du xhtml, j'utilise donc DOM (je vais peut être m'orienter vers simpleXml d'ailleurs).

Mon problème est que, lorsque je veux libérer la mémoire allouée par l'objet, ça ne fonctionne pas.

Voici mon code :
$m = memory_get_usage();
$l2xml = new LaTeX2Xml("Document LaTeX", $element, $operators);

$l2xml->parseMath(file_get_contents('file.tex'));

//libération de mémoire
$l2xml=null;

echo (memory_get_usage()-$m)/1024;
Il se trouve que quelquesoit la méthode utilisée (unset, ...) la quantité de mémoire allouée reste la même et est nettement supérieure à 0.

Je suis sous php 5.25, sans garbage collector...

Merci d'avance.

Posté : 24 mars 2009, 22:57
par naholyr
La faute au garbage collector pourri de PHP... Enfin plutôt sa gestion des références croisées : il suffit qu'un des objets ait un attribut qui a lui-même une référence vers cet objet (note : c'est le cas le plus simple, ils peuvent être plusieurs dans la boucle) et ces références implicites sont super mal gérées.

Prenons un exemple : avec une classe "ObjetLourd" qui contient une grosse chaine de caractère, et une classe "Conteneur" qui utilise un objet lourd.
class ObjetLourd
{

  public $objet = null;

  public $conteneur = null;
  
  public function __construct($conteneur )
  {
    $this->conteneur = $conteneur ;
    $this->chaine = str_repeat('azerty', 1000);
  }

}

class Conteneur
{

  public $objet = null;

  public function __construct()
  {
    $this->objet = new ObjetLourd($this);
  }

}
Quelque soit le code de la classe "Conteneur", on s'attendrait à ce que le script suivant soit à mémoire constante :
while (true) {
  $c = new Conteneur();
  //$c = null; // le mettre ou non ne change rien vu que la variable est réutilisée
}
Raté :) Allowed memory size of 134217728 bytes exhausted (tried to allocate 6001 bytes) au bout de quelques tours de boucles.

Le script sera à mémoire constante dans les deux cas suivants :

:arrow: ObjetLourd n'a pas de référence à son conteneur, dans ce cas aucun souci les références sont bien nettoyées et le script est bien à mémoire constante.
class ObjetLourd
{

  public $objet = null;

  public function __construct()
  {
    $this->chaine = str_repeat('azerty', 1000);
  }

}

class Conteneur
{

  public $objet = null;

  public function __construct()
  {
    $this->objet = new ObjetLourd();
  }

}
:arrow: L'instance de ObjetLourd n'est pas un attribut de Conteneur, mais simplement une variable du contexte de la méthode, dans ce cas la variable est supprimée et l'objet correctement détruit.
class ObjetLourd
{

  public $objet = null;

  public $conteneur = null;
  
  public function __construct($conteneur )
  {
    $this->conteneur = $conteneur ;
    $this->chaine = str_repeat('azerty', 1000);
  }

}

class Conteneur
{

  public function __construct()
  {
    $objet = new ObjetLourd($this);
  }

}

En gros, ça foire dès qu'il y a des références croisées : Conteneur a un attribut qui est une référence à l'ObjetLourd, qui a lui-même une référence vers le Conteneur. C'est pourtant un cas ultra-standard, mais dans ce cas PHP ne nettoiera rien du tout, la mémoire sera perdue ad vitam aeternam.

C'est même pire que ça : rajoute un destructeur dans Conteneur pour voir (par exemple function __destruct() { echo '!'; }), tu vas être effrayé.
Dans le premier code (référence croisée), le destructeur n'est même pas appelé ! On peut essayer d'utiliser unset() ou = null, on l'a dans le uc' :) (dans les autres cas de référence non croisées, le destructeur sera bien appelé).

Seul moyen dans cet exemple de faire fonctionner la boucle avec le premier code fourni, c'est de supprimer la source de la référence croisée : $c->objet (puisque $c->objet a un attribut qui fait référence à $c, en l'annulant on supprime la référence croisée, et ça marche) :
while (true) {
  $c = new Conteneur();
  $c->objet = null; // suppression de la référence croisée
  // là, le destructeur (et donc le nettoyage de mémoire aussi) est bien appelé
}
Voilà pour le topo, qui a priori devrait expliquer pourquoi ta mémoire n'est pas libérée. Typiquement dans DOMDocument, un noeud a une référence vers son parent, et une référence vers chacun de ses fils.
Pour conclure : ta seule chance de pouvoir gérer correctement la mémoire c'est de pouvoir supprimer toute référence à ton objet LaTeX2Xml via ses attributs (idéalement qu'il y ait un système prévu dans l'API permettant de supprimer les références croisées, comme un destructeur qui se charge de ça et qui pourrait être appelé manuellement, ou que ce soit des attributs publics). Sinon, désolé, PHP et la mémoire ça fait deux :(

Posté : 25 mars 2009, 01:13
par Hywan
D'un côté, PHP n'est pas fait pour gérer la mémoire de façon précise. C'est un langage de haut niveau, pas de bas niveau :-) (je le défends un peu même si je pourrais beaucoup le critiquer sur ce point).

Néanmoins, j'aimerais mettre en relief les nombreux efforts apportés à PHP 5.3 et son vrai garbage collector. Pour les versions antérieures, ce n'était pas forcément génial (même si les dernières versions ont fait de sacré sauts en avant), mais la 5.3 devient très correct !

Au passage Naholyr, le mot-clé var est une syntaxe PHP 4. Il serait temps de s'en débarrasser non :-) ?

Posté : 25 mars 2009, 02:26
par naholyr
Vieux reflexe quand j'écris du code vite fait, j'ai inconsciemment associé "vite fait mal fait" à "php 4" x] (j'édite merci)

Et au passage, la vraie expression quand on parle de ce que j'ai appelé "références croisées" c'est "références circulaires" (plus réaliste, puisqu'il ne s'agit pas de références qui se croisent forcément, mais plutôt d'une chaine de référence qui se mord la queue).

Posté : 25 mars 2009, 21:41
par SpintroniK
Bon c'est bien ce que je pensais, il paraît d'ailleurs que PHP 5.30 réglera ce problème :

http://blog.pascal-martin.fr/post/php-5 ... on-memoire

Je vais essayer avec simpleXml pour voir, et sinon, je coderai surement une classe Dom-Like moi même.
Quoiqu'il en soit, merci pour la réponse rapide complète et précise :d

Posté : 25 mars 2009, 21:57
par Hywan
Les références croisées existent, mais ça n'exprime pas la même chose.

Il y a deux choses à considérer (deux catégories de langages) : les langages qui permettent une gestion mémoire précise (à l'instar de C) et les autres. Pour les autres, ils se débrouillent comme ils peuvent. Souvent, ils utilisent des ramasses-miettes (ou GC pour garbage collector). Ils existent une multitude de GC, tous plus ou moins similaires.
On a à faire à des problèmes très complexes qui n'ont — a priori — pas de solutions simples et formelles (je m'y connais peu dans le fonctionnement des GC mais ça doit être passionnant).

@SpintroniK : DOM est bien plus compliqué qu'on ne l'imagine :-). Si tu veux manipuler bêtement un document XML, tu as déjà deux approches : celle de DOM ou celle de SAX. Va jeter un coup d'œil sur Wikipédia pour approfondir le sujet si le cœur t'en dit.