PHP 8.4.2 Released!

Leistungsrelevante Überlegungen

Wie bereits im vorangegangenen Abschnitt erwähnt, wirkt sich das einfache Sammeln der möglichen Wurzeln nur geringfügig auf die Leistung aus. Dies gilt jedoch nur im Vergleich zwischen PHP 5.2 und PHP 5.3. Obwohl die Aufzeichnung möglicher Wurzeln langsamer ist als wenn sie wie in PHP 5.2 überhaupt nicht aufgezeichnet werden, haben andere Änderungen an der PHP-Laufzeit in PHP 5.3 verhindert, dass dieser spezielle Leistungsverlust überhaupt sichtbar wird.

Es gibt zwei Hauptbereiche, in denen die Leistung betroffen ist. Der erste Bereich ist die verringerte Speichernutzung und der zweite Bereich ist die Laufzeitverzögerung, wenn der Mechanismus der Garbage Collection seine Speicherbereinigungen durchführt. Um diese beiden Punkte geht es im Folgenden.

Verringerte Speichernutzung

In erster Linie soll durch die Implementierung des Mechanismus der Garbage Collection der Speicherverbrauch reduziert werden, indem zirkulär referenzierte Variablen gelöscht werden, sobald die Voraussetzungen dafür erfüllt sind. In der PHP-Implementierung geschieht dies, sobald der Wurzelpuffer voll ist oder wenn die Funktion gc_collect_cycles() aufgerufen wird. Im folgenden Diagramm wird der Speicherverbrauch eines Skripts sowohl in PHP 5.2 als auch in PHP 5.3 dargestellt, wobei der Basisspeicher, den PHP selbst beim Starten verwendet, nicht berücksichtigt wird.

Beispiel #1 Beispiel für die Speichernutzung

<?php
class Foo
{
public
$var = '3.14159265359';
public
$self;
}

$baseMemory = memory_get_usage();

for (
$i = 0; $i <= 100000; $i++ )
{
$a = new Foo;
$a->self = $a;
if (
$i % 500 === 0 )
{
echo
sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
}
}
?>
Vergleich der Speichernutzung von PHP 5.2 und PHP 5.3

In diesem sehr theoretischen Beispiel wird ein Objekt erstellt, in dem eine Eigenschaft so gesetzt wird, dass sie auf das Objekt selbst zurückverweist. Wenn die Variable $a im Skript in der nächsten Iteration der Schleife neu zugewiesen wird, kommt es normalerweise zu einem Speicherleck. In diesem Fall sind zwei zval-Container betroffen (das Objekt zval und die Eigenschaft zval), aber es wird nur eine mögliche Wurzel gefunden: die Variable, die nicht gesetzt wurde. Wenn der Wurzelpuffer nach 10.000 Iterationen (mit insgesamt 10.000 möglichen Wurzeln) voll ist, setzt der Mechanismus der Garbage Collection ein und gibt den mit diesen möglichen Wurzeln verbundenen Speicher frei. Dies ist sehr deutlich in der gezackten Grafik der Speichernutzung für PHP 5.3 zu sehen. Nach jeweils 10.000 Iterationen schaltet sich der Mechanismus ein und gibt den Speicher frei, der mit den zirkulär referenzierten Variablen verbunden ist. Der Mechanismus selbst muss in diesem Beispiel nicht viel Arbeit leisten, da die Struktur, die das Leck aufweist, extrem einfach ist. Aus dem Diagramm ist ersichtlich, dass der maximale Speicherverbrauch in PHP 5.3 etwa 9 MB beträgt, während der Speicherverbrauch in PHP 5.2 stetig ansteigt.

Verzögerungen in der Laufzeit

Der zweite Bereich, in dem der Mechanismus der Garbage Collection die Leistung beeinflusst, ist die Zeit, die benötigt wird, wenn die Garbage Collection einsetzt, um den "ausgelaufenen" Speicher freizugeben. Um zu sehen, wieviel das ist, wird das vorherige Skript leicht abgeändert, um eine größere Anzahl von Iterationen zu ermöglichen und die Zahlen für die zwischenzeitliche Speichernutzung zu entfernen. Das zweite Skript ist hier:

Beispiel #2 Einfluss der GC auf die Leistung

<?php
class Foo
{
public
$var = '3.14159265359';
public
$self;
}

for (
$i = 0; $i <= 1000000; $i++ )
{
$a = new Foo;
$a->self = $a;
}

echo
memory_get_peak_usage(), "\n";
?>

Dieses Skript wird zweimal ausgeführt, einmal ist die Einstellung zend.enable_gc aktiviert und einmal ist sie deaktiviert:

Beispiel #3 Ausführen des obigen Skripts

time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
# und
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php

Auf dem verwendeten Rechner scheint der erste Befehl konstant etwa 10,7 Sekunden zu dauern, während der zweite Befehl etwa 11,4 Sekunden benötigt. Das ist eine Verlangsamung um etwa 7%. Der vom Skript maximal benötigte Speicherplatz wird jedoch um 98% von 931 MB auf 10 MB reduziert. Dieser Benchmark ist nicht sehr wissenschaftlich oder gar repräsentativ für reale Anwendungen, aber er zeigt die Vorteile, die dieser Mechanismus der Garbage Collection bei der Speichernutzung bietet. Das Gute daran ist, dass die Verlangsamung bei diesem speziellen Skript immer dieselben 7% beträgt, während die Fähigkeit, Speicher zu sparen, immer mehr Speicher einspart, je mehr zirkuläre Referenzen während der Ausführung des Skripts gefunden werden.

PHPs interne GC-Statistik

Es ist möglich, PHP etwas mehr Informationen darüber zu entlocken, wie der Mechanismus der Garbage Collection abläuft. Dazu muss PHP jedoch neu kompiliert werden, um den Code für den Benchmark und die Datenerfassung zu aktivieren. Bevor ./configure mit den gewünschten Optionen ausgeführt wird, muss die Umgebungsvariable CFLAGS auf -DGC_BENCH=1 gesetzt werden. Die folgende Befehlsfolge sollte dafür genügen:

Beispiel #4 PHP neu kompilieren, um das GC-Benchmarking zu aktivieren

export CFLAGS=-DGC_BENCH=1
./config.nice
make clean
make

Wenn der obige Beispielcode mit der neu erstellten PHP-Binärdatei erneut ausgeführt wird, wird Folgendes angezeigt, nachdem PHP die Ausführung beendet hat:

Beispiel #5 GC-Statistik

GC Statistics
-------------
Runs:               110
Collected:          2072204
Root buffer length: 0
Root buffer peak:   10000

      Possible            Remove from  Marked
        Root    Buffered     buffer     grey
      --------  --------  -----------  ------
ZVAL   7175487   1491291    1241690   3611871
ZOBJ  28506264   1527980     677581   1025731

Die aufschlussreichste Statistik wird im ersten Block angezeigt. Hier ist zu sehen, dass der Mechanismus der Garbage Collection 110 mal ausgeführt wurde und bei diesen 110 Durchläufen insgesamt mehr als 2 Millionen Speicherzuweisungen freigegeben wurden. Nachdem der Mechanismus der Garbage Collection mindestens einmal gelaufen ist, beträgt der "Root buffer peak" immer 10000.

Zusammenfassung

Im Allgemeinen führt der Garbage Collector in PHP nur dann zu einer Verlangsamung, wenn der Algorithmus für die Zyklussammlung tatsächlich ausgeführt wird, während es bei normalen (kleineren) Skripten zu keinerlei Leistungseinbußen kommen sollte.

In den Fällen, in denen der Mechanismus für die Zyklussammlung bei normalen Skripten ausgeführt wird, ermöglicht die damit verbundene Reduzierung des Speicherbedarfs die gleichzeitige Ausführung von mehr Skripten auf dem Server, da insgesamt weniger Speicher benötigt wird.

Die Vorteile sind am deutlichsten bei länger laufenden Skripten, z. B. bei langen Testsuiten oder Daemon-Skripten. Auch für » PHP-GTK-Anwendungen, die in der Regel länger laufen als Skripte für das Web, sollte der neue Mechanismus einen großen Unterschied in Bezug auf Speicherlecks machen, die sich mit der Zeit einschleichen.

add a note

User Contributed Notes 2 notes

up
16
Talisman
9 years ago
The GC, unfortunately, as expounded in the examples above, has the tendency to promote lazy programming.
Clearly the benefits of the GC to assist in memory management are there, and help to maintain a stable system, but it is no excuse to not plan and test your code properly.
Always re-read your code critically and objectively to ensure that you are not introducing memory leaks unintentionally.
up
10
Dmitry dot Balabka at gmail dot com
6 years ago
There is a possibility to get GC performance stats without PHP recompilation. Starting from Xdebug version 2.6 you are able to enable stats collection into the file (default dir /tmp with name gcstats.%p):

php -dxdebug.gc_stats_enable=1 your_script.php
To Top