Scritto da davidonzo il 08/01/2010, in Web, Tutorial
ATTENZIONE!
L'articolo che stai leggendo è stato scritto 764 giorni orsono.
Le informazioni presenti potrebbero non essere aggiornate!

Potrebbe succedere, e a me è successo giusto ieri, programmando una grossa routine in PHP, di dover istanziare oggetti che fanno riferimento ad altri oggetti. In tal caso si crea un rapporto di parentela fra oggetto padre e oggetto figlio. Il rischio è quello di ottenere un fatal error per eccesso di memoria usata.

Per capire fino in fondo, bisogna sapere che il PHP ha una gestione garbage collection interna. Quindi di regola sembrerebbe non necessario dover tracciare ogni oggetto e gestirne la sua allocazione di memoria. Ma per processi di certe dimensioni questo potrebbe non essere vero, visto che la memoria è liberata quando scade la variabile relativa, alla fine dello script.

Nella maggior parte dei casi è possibile usare unset() per liberare al volo la memoria allocata. Ma se vengono usati dei riferimenti circolari che creano relazione di tipo padre figlio fra oggetti, nonostante scada il figlio (perchè si è a fine ciclo e ne viene avviato uno nuovo), il padre resta istanziato e di conseguenza nemmeno la memoria del figlio viene riallocata come libera.

Di seguito un esempio:

<?php
class Foo{
  function __construct(){
      $this->bar = new Bar($this);
  }
}

class Bar{
  function __construct($foo = null){
    $this->foo = $foo;
  }
}
 
echo "Memoria iniziale: " . number_format(memory_get_usage(), 0, ".", ",") . " bytes\n";
for($a=0; $a<=5000; $a++){
  $foo = new Foo();
  unset($foo);
}
echo "Picco di memoria: " . number_format(memory_get_peak_usage(), 0, ".", ",") . " bytes\n";
echo "Memoria finale: " . number_format(memory_get_usage(), 0, ".", ",") . " bytes\n";
?>

Se proviamo a far girare lo script, come input avremo qualcosa del genere.

davide@laptop:~$ php public_html/memory.php 
Memoria iniziale: 329,388 bytes
Picco di memoria: 2,661,976 bytes
Memoria finale: 821,744 bytes

Si faccia caso non tanto alla poca memoria allocata, ma alla differenza fra quella iniziale, finale ed il picco. Soprattutto il picco risulta otto volte più alto della memoria inizialmente utilizzata dallo script. Ed anche alla fine si ha un'allocazione parti a tre volte l'inizio del processo. Il comando unset($foo) non ha sortito gli effetti sperati.

Su processi molto pesanti, il risultato può essere un'interruzione inaspettata a causa di un fabbisogno di memoria non corrisposto dalle potenzialità del sistema. Come risolvere? Utilizzando il distruttore di classe.

Con PHP5 sono stati introdotti i costruttori e i distruttori di classe. Per raggiungere il nostro scopo dobbiamo pensare ed usare un distruttore adeguato prima di lanciare unset. In questo modo la memoria verrà liberata adeguatamente. Di fatto trasformiamo quanto scritto sopra in quello che segue.

<?php
class Foo{
  function __construct(){
      $this->bar = new Bar($this);
  }
 
  function __destruct(){
    unset($this->bar);
  }
}

class Bar{
  function __construct($foo = null){
    $this->foo = $foo;
  }
}
 
echo "Memoria iniziale: " . number_format(memory_get_usage(), 0, ".", ",") . " bytes\n";
for($a=0; $a<=5000; $a++){
  $foo = new Foo();
  $foo->__destruct();
  unset($foo);
}
echo "Picco di memoria: " . number_format(memory_get_peak_usage(), 0, ".", ",") . " bytes\n";
echo "Memoria finale: " . number_format(memory_get_usage(), 0, ".", ",") . " bytes\n";
?>

Quello che ci serve in questo caso è un distruttore per l'istanza di Foo(), che a sua volta produce la distruzione dell'istanza della classe Bar() svincolando di fatto il rapporto di parentela fra le due classi. Il risultato lanciando lo script è sorprendente.

davide@laptop:~$ php public_html/memory.php 
Memoria iniziale: 330,028 bytes
Picco di memoria: 336,652 bytes
Memoria finale: 330,128 bytes

Le fluttuazioni fra picco, inizio e fine sono del tutto irrisorie.

Risorse esterne:

Hai trovato l'articolo interessante?
Sottoscrivi il Feed RSS per essere informato automaticamente degli ultimi aggiornamenti!
 
.Commenti rss
# 1
Articolo interessantissimo, grazie.
Di Bruno  (Inviato il 06/05/2010 @ 11:01:31)
# 2
... a me succede il contrario:
aggiungo unset([nomeClasse]) ed usa più memoria, aggiungo il metodo __destruct() con unset() di ogni proprietà e la memoria aumenta ... come mai?
Di fiscet  (Inviato il 27/05/2010 @ 15:11:13)
# 3
@fiscet: eh, bisognerebbe vedere il codice. Così non saprei proprio risponderti.
Di davidonzo  (Inviato il 27/05/2010 @ 15:15:28)
# 4
Eh sì ... effettivamente :-(
Comunque se aumento sensibilmente il contenuto delle variabili, unset() della classe mi permette di liberare memoria e trovo sempre più conveniente non invocare il distruttore, che strano
Di fiscet  (Inviato il 27/05/2010 @ 15:45:31)


I commenti possono essere moderati.
Se non lo vedi comparire subito non reinserirlo più volte.
Grazie per la gentile collaborazione.