PHP Mikro-Optimierungen und Benchmarks
Mai 2011
Dies ist eine neue Reihe von Benchmarks diverser Mikrooptimierungen unter PHP 5.3 (Windows). Einer der Zeit-Werte wurde immer auf 10 ms skaliert, um einen besseren Vergleich zwischen den verschiedenen Benchmarks zu ermöglichen.
Wie immer gilt, dass sich nachträgliche Optimierungen nur selten lohnen. Man sollte von Beginn an optimal programmieren.
Identisch ist schneller als gleich.
Dasselbe gilt auch für !=/!==.
Typecasting mit (int) ist schneller als intval().
Prüfen, ob eine Zahl gerade/ungerade ist.
Inkrementierung mit ++$i ist schneller als $i++.
Dasselbe gilt auch für die Dekrementierung mit $i-- bzw. --$i.
Plus eins: ++$i ist schneller als $i+=1 und viel schneller als $i=$i+1.
Halbieren mit Bit-Shift ist schneller als durch 2 teilen.
Bei der Verdoppelung bzw. Bit-Shift nach links tritt dieser Unterschied kaum auf.
Einfache Anführungszeichen und Stringverkettung benutzen.
echo ist schneller als print.
Stringverkettung ist langsamer als mehrere echos.
$array[] ist schneller als array_push().
isset() ist schneller als array_key_exists().
in_array() ist sehr langsam.
Bei zwei Checks:
Funktionen innerhalb von Schleifen-Parametern sind sehr kostspielig.
htmlspecialchars() ist schneller als htmlentities().
Wenn man nur die HTML/XML-Steuerzeichen als Entities kodieren will, ist es angebracht htmlspecialchars() zu verwenden.
Keyword global ist schneller als $GLOBALS.
Zweimaliger Zugriff:
Bemerkung: Es gibt keine nennenswerte Unterschiede zwischen Lese- und Schreibzugriffen.
Optionaler Parameter nicht an Funktion übergeben, wenn er dem Standard-Wert entspricht.
Zugriff auf Variablen, Arrays und Objekte.
Direkter Zugriff auf potentiell inexistente Variabeln vermeiden.
OOP: Nicht-statische Aufrufe sind schneller als statische.
Des weiteren verursacht der statische Aufruf hier eine Strict-Meldung.
Fehlermeldungen nicht mit einem @ unterdrücken.
Fehlermeldungen, Notices und Strict-Standards nicht unterdrücken sondern vermeiden.
strtr() ist schneller als str_replace(), dieses ist schneller als preg_replace() und dieses wiederum ist schneller als ereg_replace().
Des Weiteren empfiehlt es sich, ganz auf ereg_*-Funktionen zu verzichten.
Benutze unset() um den Speicher wieder freizugeben. Dies lohnt sich allerdings nur bei grösseren Variablen wie längeren Strings oder Arrays.
Bei MySQL-Daten kann man mysql_free_result() verwenden.
Includes (include(), require()) sollten wenn möglich verhindert werden, da jeder Include ein erneuter Zugriff auf das Dateisystem und somit zusätzliche Festplattenaktivität bewirkt und der PHP-Parser nochmal ans Werk muss.
Falls eine nicht-PHP-Datei eingebunden werden muss, sollte readfile() benutzt werden.
Wie immer gilt, dass sich nachträgliche Optimierungen nur selten lohnen. Man sollte von Beginn an optimal programmieren.
Identisch ist schneller als gleich.
<?php
($a == $b) // gleich
// unteres ist schneller als oberes
($a === $b) // identisch
?>
gleich: 10.0 ms
identisch: 5.5 ms
PHP wendet bei Vergleichen immer implizites Typecasting an, wodurch Zeit verloren geht.Dasselbe gilt auch für !=/!==.
Typecasting mit (int) ist schneller als intval().
<?php
$zahl = intval($str);
// unteres ist schneller als oberes
$zahl = (int)$str;
?>
intval(): 10.0 ms
(int): 4.1 ms
Anmerkung: intval() bietet noch eine zusätzliche Funktion, welche jedoch meist nicht benutzt wird.Prüfen, ob eine Zahl gerade/ungerade ist.
<?php
if( $int % 2 ); // ungerade, mit Modulo-Operator
// unteres ist schneller als oberes
if( $int & 1 ); // ungerade, mit Bit-Operator
?>
Modulo-Operator: 10.0 ms
Bit-Operator: 8.7 ms
Inkrementierung mit ++$i ist schneller als $i++.
<?php
$i++; // Postinkrement
// unteres ist schneller als oberes
++$i; // Präinkrement
?>
$i++: 10.0 ms
++$i: 7.3 ms
Anmerkung: $i++ und ++$i haben nicht denselben Rückgabewert, was aber z.B. bei for-Schleifen keine Rolle spielt.Dasselbe gilt auch für die Dekrementierung mit $i-- bzw. --$i.
Plus eins: ++$i ist schneller als $i+=1 und viel schneller als $i=$i+1.
++$i: 10.0 ms
$i+=1: 11.3 ms
$i=$i+1: 27.1 ms
Dasselbe gilt auch für minus eins.Halbieren mit Bit-Shift ist schneller als durch 2 teilen.
<?php
$int / 2; // geteilt durch 2
// unteres ist schneller als oberes
$int >> 1; // Bit-Shift rechts (geteilt durch 2)
?>
/2: 10.0 ms
>>1: 6.8 ms
Der Grund scheint darin zu liegen, dass bei der normalen Division mit Floatingpoint-Zahlen gerechnet wird anstatt mit Integern.Bei der Verdoppelung bzw. Bit-Shift nach links tritt dieser Unterschied kaum auf.
Einfache Anführungszeichen und Stringverkettung benutzen.
<?php
$str = "Ich heisse $name";
// unteres ist schneller als oberes
$str = 'Ich heisse '.$name;
?>
Da PHP bei doppelten Anführungszeichen den String nach Variablen durchsucht, geht Zeit verloren, die Stringverkettung ist im Vergleich schneller.oberes: 10.0 ms
unteres: 6.3 ms
echo ist schneller als print.
<?php
print 'Text';
// unteres ist schneller als oberes
echo 'Text';
?>
print: 10.0 ms
echo: 8.7 ms
Stringverkettung ist langsamer als mehrere echos.
<?php
echo $var1.$var2.$var3.$var4.$var5;
// unteres ist schneller als oberes
echo $var1, $var2, $var3, $var4, $var5;
echo $var1; echo $var2; echo $var3; echo $var4; echo $var5;
?>
Verkettung: 10.0 ms
Komma-getrennt: 7.5 ms
mehrere echo: 7.5 ms
$array[] ist schneller als array_push().
<?php
array_push($array, 'value');
// unteres ist schneller als oberes
$array[] = 'value';
?>
array_push(): 10.0 ms
$array[]: 4.4 ms
isset() ist schneller als array_key_exists().
<?php
array_key_exists($key, $array);
// unteres ist schneller als oberes
isset($array[$key]);
?>
array_key_exists(): 10.0 ms
isset(): 0.8 ms
Anmerkung: Falls der Wert von $array[$key] gleich null ist, geben die beiden Funktionen nicht dasselbe Resultat zurück.in_array() ist sehr langsam.
<?php
in_array($value, $array);
// unteres ist schneller als oberes
$valuesAsKey = array_count_values($array);
isset($valuesAsKey[$value]);
?>
Wenn man mehr als viermal prüfen will, ob ein bestimmter Wert in einem Array existiert, ist es schneller zuerst mit array_count_values() die Werte als Keys zu speichern und dann mehrmals isset() darauf anzuwenden.Bei zwei Checks:
in_array(): 5.9 ms
isset(): 10.0 ms
Bei zehn Checks:in_array(): 29.8 ms
isset(): 10.2 ms
Jedes zusätzliche in_array() kostet enorm viel mehr als ein zusätzliches isset.Funktionen innerhalb von Schleifen-Parametern sind sehr kostspielig.
<?php
// nicht so:
for($i=0; $i<count($array); ++$i){ }
// sondern so:
$total = count($array);
for($i=0; $i<$total; ++$i){ }
?>
Bei der oberen Methode muss die Funktion, hier count(), bei jedem Durchgang ausgeführt werden, bei der unteren wird sie insgesamt nur einmal aufgerufen.htmlspecialchars() ist schneller als htmlentities().
Wenn man nur die HTML/XML-Steuerzeichen als Entities kodieren will, ist es angebracht htmlspecialchars() zu verwenden.
htmlspecialchars(): 10 ms
htmlentities(): 32 ms
Aufgrund des grösseren Funktionsumfangs von htmlentities() ist der Geschwindigkeitsunterschied nachvollziehbar.Keyword global ist schneller als $GLOBALS.
<?php
function A(){ global $var; $var = 42; }
function B(){ $GLOBALS['var'] = 42; }
?>
Wird die globale Variable nur einmal verwendet, ist Funktion B mit $GLOBALS minimal schneller als Funktion A mit global.global: 10.0 ms
$GLOBALS: 8.2 ms
Bei mehrfacher Verwendung der Variable ist global schneller als $GLOBALS.Zweimaliger Zugriff:
global: 12.4 ms
$GLOBALS: 15.9 ms
Dreimaliger Zugriff:global: 14.7 ms
$GLOBALS: 50.6 ms
Je mehr Zugriffe, desto schneller ist Variante A.Bemerkung: Es gibt keine nennenswerte Unterschiede zwischen Lese- und Schreibzugriffen.
Optionaler Parameter nicht an Funktion übergeben, wenn er dem Standard-Wert entspricht.
<?php
function func($a=1, $b=2, $c=3){
// Beispiel-Funktion mit optionalen Parametern
}
// Aufruf ohne Parameter:
func();
// Aufruf mit den Standard-Werten:
func(1, 2, 3);
?>
Die beiden Aufrufe machen genau das gleiche, doch ist die obere Variante ohne Parametern schneller.func(): 10.0 ms
func(...): 12.8 ms
Zugriff auf Variablen, Arrays und Objekte.
$v: 1.0 ms Einfache Variable
$a[6]: 3.0 ms Eindimensionales Array mit numerischen Indizies
$a[6][7][8]: 9.0 ms Dreidimensionales Array mit numerischen Indizies
$a['s']: 3.8 ms Eindimensionales Array mit String-Indizies
$a['s']['t']['u']: 11.3 ms Dreidimensionales Array mit String-Indizies
$o->p: 6.3 ms Einfache Objekt-Eigenschaft
$o->p->q->r: 20.5 ms Verschachtelte Objekt-Eigenschaft
Fazit: Ein Zugriff auf einen Array-Wert mit numerischem Index ist schneller als auf einen mit einem String-Index. Zugriffe auf Array-Werte sind schneller als Zugriffe auf Objekt-Eigenschaften.Direkter Zugriff auf potentiell inexistente Variabeln vermeiden.
<?php
// Existenz prüfen, Notice-Meldung umgehen
$var = isset($_GET['var']) ? $_GET['var'] : '';
// Existenz nicht prüfen, Notice-Meldung unterdrücken
$var = @$_GET['var'];
?>
Wenn die Variable nicht existiert, ergeben sich folgende Werte:umgehen: 10.0 ms
unterdrücken: 40.9 ms
Wenn die Variable existiert, verhält es sich umgekehrt, aber mit kleinerer Differenz.umgehen: 10.0 ms
unterdrücken: 4.1 ms
OOP: Nicht-statische Aufrufe sind schneller als statische.
<?php
class A {
function func(){ }
}
// Statischer Aufruf
A::func();
// Dynamischer Aufruf
$a = new A();
$a->func();
?>
Obwohl der nicht-statische Aufruf doppelt soviel Code umfasst, ist er schneller.Des weiteren verursacht der statische Aufruf hier eine Strict-Meldung.
statisch: 10.0 ms
dynamisch: 7.3 ms
Falls die Funktion das Keyword static besitzt, ist der statische Aufruf schneller, allerdings nur, weil beim nicht-statischen Aufruf zuerst ein Objekt erzeugt werden muss.<?php
class B {
static function func(){ }
}
// Statischer Aufruf
B::func();
// Dynamischer Aufruf
$b = new B();
$b->func();
?>
statisch: 3.9 ms
dynamisch: 7.2 ms
dynamisch: 2.4 ms (exklusive 'new Object')
Zum Vergleich: Eine standalone (prozedurale) Funktion.<?php
function func(){ }
func();
?>
standalone: 1.3 ms
Fehlermeldungen nicht mit einem @ unterdrücken.
<?php
// nicht so:
@unlink('mich-gibts-nicht.dat');
// sondern so:
if(file_exists('mich-gibts-nicht.dat'))
unlink('mich-gibts-nicht.dat');
?>
Wenn in diesem Beispiel die zu löschende Datei nicht existiert, ist file_exists() schneller als @unlink().Fehlermeldungen, Notices und Strict-Standards nicht unterdrücken sondern vermeiden.
<?php
// nicht so:
$wert = array_shift(explode($str, $array)); // erzeugt eine Strict-Meldung
// sondern so:
$werte = explode($str, $array));
$wert = array_shift($werte);
// ----
// nicht so:
if($gibtsnicht){ } // erzeugt eine Notice
// sondern so:
if(!empty($gibtsnicht)){ }
?>
Fehlermeldungen jeglicher Art, ob sie unterdrückt werden oder nicht, verlangsamen das Script.strtr() ist schneller als str_replace(), dieses ist schneller als preg_replace() und dieses wiederum ist schneller als ereg_replace().
Des Weiteren empfiehlt es sich, ganz auf ereg_*-Funktionen zu verzichten.
Benutze unset() um den Speicher wieder freizugeben. Dies lohnt sich allerdings nur bei grösseren Variablen wie längeren Strings oder Arrays.
Bei MySQL-Daten kann man mysql_free_result() verwenden.
Includes (include(), require()) sollten wenn möglich verhindert werden, da jeder Include ein erneuter Zugriff auf das Dateisystem und somit zusätzliche Festplattenaktivität bewirkt und der PHP-Parser nochmal ans Werk muss.
Falls eine nicht-PHP-Datei eingebunden werden muss, sollte readfile() benutzt werden.

Andere Einträge
- Dateiendung erkennen
- array_map_recursive()
- Dateien mit .htaccess gzip komprimieren
- Geschwindigkeit von PHP-Scripts optimieren
- is_utf8() – auf UTF-8 prüfen
Gruß
hoffe es kommt noch ein teil nummero 3
Wie wäre es mit einem Tool, dass man auf dem eigenen Server einsezten könnte? So könnte man die Ausführungszeiten z.B. auch mal unter PHP 5.4 unter die Lupe nehmen.
Vielen Dank.
$werte = explode($str, $array));
$wert = array_shift($werte);
extra noch eine weitere Variable erstellen muss. Ist das nicht theoretisch 'viel' mehr Rechenaufwand? Ich neige immer sehr dazu, '1x-Variablen' zu vermeiden.
"Vielen Dank das du dir die Zeit genommen hast das zusammen zu stellen! Trotzdem ist mir leider nicht klar, wieso man hier
$werte = explode($str, $array));
$wert = array_shift($werte);
extra noch eine weitere Variable erstellen muss. Ist das nicht theoretisch 'viel' mehr Rechenaufwand? Ich neige immer sehr dazu, '1x-Variablen' zu vermeiden."
.. Nein, das ist kein großer Mehraufwand. Intern wird das komplett identisch verarbeitet
Bzgl. Kommentar #5: Wenn es Deiner Meinung nach realitätsferner Schwachsinn ist, schon bei der ersten Zeile Code möglichst optimal zu programmieren, solltest Du vielleicht lieber nicht mit mehr als einer Person zusammenarbeiten.
Es ist sicherlich richtig, dass man nicht mittendrin mit Benchmarks und Mikrooptimierung anfangen sollte, aber von vornherein (int) statt intval() zu nutzen, Funktionen in Schleifenparametern zu vermeiden oder strcmp() gegen (string) und === zu tauschen macht IMMER Sinn, selbst wenn Du die wirklichen Probleme noch nicht kennen solltest.
lg Flo