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.
<?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 ); // ungerade, mit Modulo-Operator
// unteres ist schneller als oberes
if( $int ); // 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(123);
?>
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 {
    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 {
    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


Kommentare

#1
von Viktor am 23.01.2012
Sehr gute Zusammensetzung. Vielen Dank!

Gruß

#2
von Andreas am 17.04.2012
Ich möchte mich an dieser Stelle auch bedanken, wirklich sehr plausibel erklärt und zudem noch die ergebnise. so konnte ich meinem cms die handbremse entfernen :)

hoffe es kommt noch ein teil nummero 3

#3
von Kerstin am 27.10.2012
Vielen Dank für die sehr gute Zusammenstellung, auch die Erklärungen!!
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.

#4
von Sebb am 10.02.2013
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.

#5
von the unreal small baus that loves you am 12.11.2013
@Sepp
"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

#6
von Joey am 08.12.2013
Erstmal herzlichen Dank für die informative Auflistung.

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.

#7
von Flo Toys am 22.07.2015
Hey, eine wirklich tolle Übersicht. Interessant wären sicherlich noch die absoluten Zeitwerte, damit man sehen kann, ob sich eine Optimierung auch wirklich lohnt oder ob die Optimierung so minimal ist, dass man daraus wenig Nutzen hat.

lg Flo