PHPMemoryUsage
Aus XT Knowledge Base
Inhaltsverzeichnis |
PHP Speicherverbrauch
Bei der Erzeugung eines Trees für die Spracherkennung von Texten bin ich plötzlich an ein Speicherlimit bei 32MB gestoßen und habe nicht schlecht gestaunt, vor allem weil der Tree nicht einmal 10.000 Nodes enthield. Ich benutze PHP 5.2.0, bin also auf dem aktuellen Stand. Um der Sache auf die Spur zu kommen, habe ich eine Analyse angestellt, die mir Klarheit über den Speicherverbrauch bringen soll. Hier das Ergebnis:
Testumgebung
Als Testumgebung dient mir mein Webserver Apache unter Linux mit PHP 5.2.0. Den Speicherverbrauch messe ich mit der Funktion [1], die seit PHP 4.3.2 zur Verfügung steht.
Als erstes gebe ich einfach mal den Speicherverbrauch beim Start eines PHP-Scripts aus:
<? echo memory_get_usage(); ?>
Ergebnis:
83808
memory_get_usage() liefert nicht den tatsächlich vom System angeforderten Speicher zurück, sondern lediglich der tatsächlich von PHP verwendete Speicher. PHP allokiert größere Bereiche und vergibt sie dann Häppchenweise an den Interpreter, sobald dieser Speicher anfordert.
Dieser Speicher wird für die vordefinierten Arrays verwendet wie z.B. $GLOBALS und kann variieren, je nach Kontext des Aufrufs. Was $GLOBALS enthält kann mit folgendem Code ermittelt werden:
<textarea cols=100 rows=20> <?print_r( $GLOBALS );?> </textarea>
Weitere Informationen über vordefinierte Variablen findet man unter Predefined Variables.
Um den Verbrauch zu messen speichere ich alse den Speicherverbrauch beim Start des Scripts in eine Variable und geben am Ende die Differenz aus:
<? $m1 = memory_get_usage(); echo memory_get_usage()-$m1; ?>
Das Ergebnis verblüfft auf den ersten Blick - spontan würde man als Ergebnis eine 0 erwarten, ausgegeben wird aber 72. Es scheint also, als würde die Variable $m1 bereits mit 72 ins Gewicht fallen.
Angenommen, dieser Verdacht wäre richtig: Was wäre dann das Ergebnis folgenden Scripts:
<? $m1 = memory_get_usage(); $m2 = memory_get_usage(); echo $m2-$m1; ?>
Das Ergebnis ist ebenfalls 72 und so scheint es, als wäre es nicht so einfach, nachvollziehbare und erklärbare Ergebnisse zu erhalten. Die Messung hat Einfluss auf das Ergebnis.
Folgender Test macht die Verwirrung komplett:
<? $m1 = memory_get_usage(); $m2 = memory_get_usage(); $m2 = memory_get_usage(); echo $m2-$m1; ?> Das Ergebnis ist 192. Die Variable $m2 wird offenbar erst angelegt, nachdem der Aufruf von memory_get_usage() abgeschlossen ist.
Daraus folgt, dass der ideale Testcode folgendermaßen aufgebaut sein muss:
<? $m1 = memory_get_usage(); $m1 = memory_get_usage(); // add tests from here --> // <-- until here echo memory_get_usage()-$m1; ?>
Das Ergebnis ist tatsächlich 0 wie erwartet. Glücklicherweise belegen Kommentare keinen Speicher ;o) Zum Abschluss ein Test ohne Variable:
<? echo memory_get_usage()."<br>\n"; echo memory_get_usage()."<br>\n"; echo memory_get_usage()."<br>\n"; ?>
Ergebnis:
85104 85232 85232
Ob nun echo oder memory_get_usage() beim ersten Aufruf Speicher belegt, ist ein Rätsel, das ich an dieser Stelle nicht lösen möchte...
Einfache Variablen
Für folgende Tests füge ich den angegebenen Code in das oben erarbeitete Snippet ein:
<? $m1 = memory_get_usage(); $m1 = memory_get_usage(); // add tests from here --> // <-- until here echo memory_get_usage()-$m1; ?>
Boolean
$b = true;=> 192 Bytes
Integer
$i = 100;=> 192 Bytes
192 Bytes Speicherverbrauch für eine einzige Integer-Variable erscheint mächtig übertrieben und für Optimierungen seitens der PHP-Implementation scheint es noch jede Menge Spielraum zu geben.
Floating point number
$f = 3.1415;=> 192 Bytes
String
$s = "Hallo Welt!";=> 248 Bytes
$s = "Hallo, du ganz große Welt mit fünf Kontinenten und sieben Weltmeeren!";=> 312 Bytes
$s = "Das ist ein Text, der der maximalen Länge des Textes entspricht, wie er bei Twitter ". "für Kurznachrichten erlaubt ist, also genau 140 Zeichen.";=> 384 Bytes
Komplexe Variablen
Arrays
$a = array();Ein leeres Array belegt bereits beachtliche 424 Bytes!
$a = array( 1 );Ein Integerelement vergrößert den belegten Speicher um 192 Bytes auf 626 Bytes - also die Größe, die auch ein Integer alleine belegen würde.
$a = array( 1, 2 );=> 808 Bytes. Der Speicherverbrauch scheint linear zu wachsen...
$a = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 );=> 2408 Bytes (2408-424)/10 = 198.4 Bytes pro Element. Das Wachstum scheint nicht wirklich linear zu sein...
Maps
$a = array( "a" => 0 );=> 616 Bytes. Obwohl man annehmen könnte, dass hier wenigstens ein String und ein Integer für das erste Element benötigt werden, scheint doch der Platz eines Integers auszureichen..
$a = array( "a" => 0, "b" => 1, "c" => 10 );=> 1000 Bytes (1000-424)/3 = 192. Man spart offenbar keinen Speicherplatz, wenn man normale Arrays assoziativen Arrays vorzieht!
Objekte
class EmptyClass { }; $obj = new EmptyClass;
Das Erzeugen einer Instanz einer leeren Klasse beansprucht bereits 496 Bytes...
class EmptyClass { private $a = 0; }; $obj = new EmptyClass;
... und wächst mit dem Hinzufügen einer Membervariablen vom Typ Integer moderat um 136 auf 632 Bytes. Wobei 16 Byte mehr verbraucht werden, wenn die Membervariable private anstatt public deklariert wird.
class SimpleClass { private $a; function SimpleClass() { $this->a = 0; } }; $obj1 = new SimpleClass();
Das Einfügen eines Konstruktors hat ebenfalls Auswirkung auf den Speicherverbrauch. Eine Instanz dieser Klasse benötigt nun 936 Bytes. Allerdings scheint das Einfügen weiterer Funktionen keine Auswirkung mehr auf den Speicherverbrauch der Objekte zu haben:
class SimpleClass { private $a; function SimpleClass( $a ) { $this->a = $a; } function getValue( ) { return $this->a; } }; $obj1 = new SimpleClass( 10 );
=> 936 Bytes
class SimpleClass { private $a; function SimpleClass( $a ) { $this->a = $a; } function getValue( ) { return $this->a; } }; $obj1 = new SimpleClass( 10 ); $obj2 = new SimpleClass( 20 ); $obj3 = new SimpleClass( 30 );
=> 2416 Bytes (2416 / 3 = 805.33) Dieses Ergebnis lässt darauf schließen, dass die Deklaration der Klasse selbst Speicher belegt. Die Überprüfung mit 2, 3 und 4 Objekten ergibt keinen Anhaltspunkt darauf, wieviel Speicher das sein könnte. Während das erste Objekt 936 bytes belegt, werden mit dem zweiten Objekt 704 Bytes belegt, mit dem dritten 776 und mit dem vierten 637.
Immerhin kann man erkennen, dass der Garbage-Collector funktioniert, wenn man die neuen Objekte immer der gleichen Variable zuweist:
class SimpleClass { private $a; function SimpleClass( $a ) { $this->a = $a; } function getValue( ) { return $this->a; } }; $obj1 = new SimpleClass( 10 ); $obj1 = new SimpleClass( 20 ); $obj1 = new SimpleClass( 30 );
=> 1712 Bytes
Fazit
Zusammenfassend kann man sagen, dass PHP extrem verschwenderisch mit dem Speicher umgeht und man kann nur hoffen, dass an dieser Stelle zukünftig optimiert wird und nicht nur an der Geschwindigkeit. Zumal ein geringerer Speicherverbrauch gleichzeitig auch die Programme langsamer macht und somit eine Optimierung am Speicherverbrauch mit Sicherheit auch eine schnellere Ausführungsgeschwindigkeit nach sich ziehen würde.