Die Performance des Objektmodells von PHP5 im Vergleich zu PHP4

Überraschung bei der Migration. Das neue Objektmodell von PHP5 bietet gegenüber dem von PHP4 eine ganze Reihe von Vorteilen. Endlich ist es möglich, einen Großteil der im Rahmen der objektorientierten Programmierung üblichen Verfahren einzusetzen. Das Resultat ist gut wartbarer, übersichtlich strukturierter und in der Regel auch schlankerer Code. Um so mehr überrascht es, wie sich nach der Migration eines Projekts von PHP4 nach PHP5 zeigte, dass die Performance zum Teil spürbar nachgelassen hat.

Der erste Verdächtige für diese Performance-Einbußen ist wohl das neue Objektmodell selbst. PHP ist im Kern keine objektorientierte Sprache, die Objekt-Features sind ähnlich wie bei Perl "sekundär". Das bedeutet auch, dass der Aufwand für den Interpreter der Komplexität des Objektmodells proportional ist. So fordert zum Beispiel die Prüfung, ob die Implementierung eines Interfaces korrekt ist, der Skript-Engine ganz offenbar einiges an Arbeit ab. Überraschend ist trotzdem, dass die Performance-Einbußen so deutlich sind im direkten Vergleich von PHP5 zu PHP4 - zumal Zend beansprucht, PHP5 sei sogar schneller als PHP4.

Rahmenbedingungen. Benchmarks sollten genaueren Aufschluss geben, welche Objekt-Features wie viel an Mehraufwand bedeuten. Zur Prüfung wurden die PHP-Versionen 4.3.10 und 5.0.3 in der CLI Variante mit der Option --enable-memory-limit compiliert. Die Tests liefen auf einer Maschine mit 1.1 GHz AMD Prozessor unter Linux 2.4.20 (Debian) mit 512MB RAM. Gemessen wurde die absolute Laufzeit, was gerechtfertigt erscheint, da das System während der Tests keinen zusätzlichen Belastungen unterlag. Keine der Testmethoden ruft I/O-Funktionen auf. Die Tests wurden unter verschiedenen Bedingungen in Schleifen von 100.000 bis 1.000.000 Durchläufen ausgeführt und die Ergebnisse dann gemittelt. Zudem wurde die Differenz des Speicherverbrauchs vor und nach Ausführung jeder Schleife gemessen; idealerweise sollte diese bei Null liegen.

Die Messungen beginnen in jedem Fall erst nach dem kompletten Parserdurchlauf, so dass die Performance des Parsers nicht Gegenstand des Benchmarks ist. Die Ausführung der for-Schleife selbst ist unter PHP5 übrigens sogar ein wenig schneller als uner PHP4. Dies haben wir geprüft, um auszuschließen, dass die Messergebnise von dieser Seite aus beeinflusst werden.

Methoden. Da es für viele der neuen Objekt-Features kein Äquivalent in PHP4 gibt, wurden die betreffenden Eigenschaften für die 4er-Version in geeigneter Weise simuliert. So wurde etwa als Gegenbeispiel für die Implementierung eines Interface oder die Vererbung einer abstrakten Klasse die einfache Erweiterung einer Klasse benutzt. Das Zerstören eines Objekts ruft in PHP4 keinen Destruktor auf, in PHP5 den mit __destruct() definierten. Die statische Variable von PHP5 wurde in PHP4 mit einer globalen Variable, die Klassen-Konstante mit einer Objekt-Eigenschaft nachgebildet.

Die Methoden werden nun im Einzelnen aufgeführt und kurz beschrieben:

  • bench_create - ein Objekt wird erzeugt
  • bench_destroy - ein Objekt wird erzeugt und explizit zerstört
  • bench_create_get_string - ein Objekt wird erzeugt und eine Methode aufgerufen, die einen String zrückgibt
  • bench_create_get_array - ein Objekt wird erzeugt und eine Methode aufgerufen, die einen Array zrückgibt
  • bench_create_get_boolean - ein Objekt wird erzeugt und eine Methode aufgerufen, die einen Boolean-Wert zrückgibt
  • bench_create_get_static - ein Objekt wird erzeugt und eine Methode aufgerufen, die eine statische Variable (Integer) zrückgibt
  • bench_create_get_constant - ein Obekt wird erzeugt und eine Klassenkonstante ausgelesen
  • bench_create_clone - ein Objekt wird kopiert
  • bench_create_ref - ein Objekt wird referenziert (PHP4 $a =& $b, PHP5 $a = $b)
  • bench_pass_obj - ein Objekt wird an eine Methode übergeben, die es zurückgibt (PHP4 als Referenz)
  • bench_create_extends - eine Klasse, die eine andere Klasse erweitert wird instanziiert
  • bench_method_extends - eine Klasse, die eine andere Klasse erweitert wird instanziiert und eine Methode der Instanz aufgerufen
  • bench_create_abstract_impl - eine Klasse, die eine abstrakte Klasse erweitert wird instanziiert
  • bench_method_abstract_impl - eine Klasse, die eine abstrakte Klasse erweitert wird instanziiert und eine Methode der Instanz aufgerufen
  • bench_create_interface_impl - eine Klasse, die ein Interface implementiert wird instanziiert
  • bench_method_interface_impl - eine Klasse, die ein Interface implementiert wird instanziiert und eine Methode der Instanz aufgerufen

Ergebnisse. Die gute Nachricht zuerst: die neue Speicherverwaltung von PHP5 macht ihre Arbeit ganz offenbar tatsächlich besser als die von PHP4. In der Regel ist der Gradient im Speicherverbrauch geringer, nur in seltenen Fällen dem von PHP4 gleich. Einzig das Klonen eines Objekts mit clone verbraucht mehr Speicher als das Äquivalent in PHP4 (die Gleichsetzung zweier Objekte mittels des =-Zeichens).

Diagramm

Auf der anderen Seite zeigt das Laufzeitverhalten von PHP5 verglichen mit dem von PHP4 ein sehr uneinheitliches Bild. Das Erzeugen und das Zerstören eines Objekts sind in der Tat schneller erledigt, ebenso das Erzeugen einer Objektreferenz. Alle anderen getesteten Features benötigen unter PHP5 jedoch mehr Zeit als unter PHP4.

Der zusätzliche Aufwand liegt bei unseren Tests zwischen 3 und 51 Prozent. Dabei schneiden die Implementierungen von Interfaces oder das Vererben abstrakter Klassen besonders schlecht ab. Das Auslesen einer Klassen-Konstanten erweist sich ebenfalls als teuer. Deutlich geringer fallen die Leistungsunterschiede bei Features ins Gewicht, die auch PHP4 schon unterstützt. Trotzdem sind sie auch dort merklich. Möglicherweise kostet die Unterscheidung der Sichtbarkeit nach private, protected und public nun doch einiges an Rechenzeit.

Diese Ergebnisse treffen so zumindest für die CLI Variante von PHP zu. Erste Versuche mit dem Apache-Modul zeigen jedoch, dass die Tendenz sich auch für diese Variante bestätigt.

Fazit. Das alles bedeutet jedoch nicht, dass auf die Benutzung der neuen OOP-Festures verzichtet werden sollte. Der Gewinn an Strukturiertheit, damit auch Zuverlässigkeit und Wartbarkeit rechtfertigt geringe Leistungseinbußen durchaus. Abzuraten ist jedoch zumindest für Webprojekte von der Verwendung von Klassen-Konstanten sowie abstrakten Klassen/Methoden und Interfaces. Eventuell können letztere bei der Entwicklung benutzt werden, um die Einhaltung von Standards zu garantieren. Auf dem Produktivsystem könnte die implements-Klausel dann auskommentiert werden, um dem Leistungseinbruch zu entgehen. Im Einzelfall bleibt immer neu zu entscheiden, ob Zuverlässigkeit oder Leistung im Vordergrund steht.

Nachtrag. Erste Tests mit PHP 5.1.0b3 zeigen, dass es Zend offenbar gelungen ist, die schlechte Performance von PHP5 in den Griff zu bekommen. Tatsächlich ist der Performancegewinn gegenüber PHP4 nun sehr deutlich. Wir werden an dieser Stelle einen ausführlichen Vergleich zwischen PHP 5.0.x und PHP 5.1.x nachreichen, sobald die endgültige Version ausgeliefert wird (siehe Benchmark 5.1.0).