Tipps zur Performance Optimierungen von PHP-Scripten im Kleinen

Montag, der 14. Januar 2008. Veröffentlicht von René.

Der Webserver ist mittlerweile total überlastet? Der Seitenaufbau dauert zu lange? Spätestens jetzt macht man sich auf die Suche nach Optimierungsmöglichkeiten. Natürlich ist es auch ratsam schon von Anfang an, bei den ersten Zeilen eines neuen Scripts, schon an die Performance zu denken und unnötige Performancebremsen gar nicht erst einzubauen. Hier ein par praktisch auch umsetzbare Tipps zum Optimieren von PHP-Scripten in Hinsicht auf die Performance:

  • Einfache statt doppelter Anführungszeichen

    Es macht einen wichtigen Unterschied ob man einfache oder doppelte Anführungszeichen verwendet.

    print( "Hallo, dies ist ein Test" );

    Der obige Code benötigt mehr Rechenzeit als die Variante mit einfachen Anführungszeichen:

    print( 'Hallo, dies ist ein Test' );

    Warum ist einfach erklärt: Wenn doppelte Anführungszeichen verwendet werden, wird die komplette Zeichenkette aufwendig auf eingeschlossene Variablen überprüft. Wenn die Zeichenkette keine eingeschlossenen Variablen verwendet ist diese Prüfung sogar unnötig.
    Wenn man in einer Zeichenkette mit einfachen Anführungszeichen eine Variable verwenden möchte, geht das so:

    print( 'Hallo '.$test.', dies ist ein Test' );

    statt:

    print( "Hallo $test, dies ist ein Test" );

    Man sollte sich meiner Meinung nach auch wegen der einfacheren Lesbarkeit angewöhnen, immer die Schreibweise mit einfachen Anführungszeichen zu verwenden.

  • Keine unnötigen Funktionsaufrufe in Abbruchbedingungen von Schleifen

    Die folgende unnötige Rechenzeit-Verschwendung sieht man leider sehr oft:

    $array = array( /*...100 Elemente...*/ );
    for( $i=0; $i<count($array); $i++ )
    {
      // ...
    }

    Folgende Implementierung ist zum Beispiel schneller:

    $array = array( /*...100 Elemente...*/ );
    $array_count = count( $array );
    for( $i=0; $i<$array_count; $i++ )
    {
      // ...
    }

    Es werden 99 Aufrufe der Funktion count(…) gespart.

  • Schleifen in Schleifen vermeiden

    Manchmal nur wenige Zeilen Code, aber verschachtelten Schleifen haben es in sich. In folgendem Beispiel wird die Funktion test() zum Beispiel 1.000.000 mal aufgerufen, da kommt schnell einiges zusammen:

    for( $a=0; $a<100; $a++ )
    {
      for( $b=0; $b<100; $b++ )  
      {
        for( $c=0; $c<100; $c++ )  
        {
          test();
        }
      }
    }

    Wenn möglich sollte man verschachtelte Schleifen vermeiden. Wenn das nicht geht sollte man den Code innerhalb der Schleifen so performant wie möglich gestalten.

  • Immer zu erst das Wahrscheinlichste abprüfen

    Wenn mehrere Prüfungen auf einander folgen sollte man die wahrscheinlichste oder evtl. auch performanteste Bedinung immer zuerst prüfen. Auch in einem If-Statement wird die zweite Prüfung nicht mehr ausgeführt wenn schon die erste Bedingung zur Ermittlung des Ergebnisses genügt.
    In folgendem Beispiel gehen wir davon aus, das $test meist 1 entspricht. Die Prüfung ob $test gleich 2 ist, muss dann in diesem Beispiel gar nicht mehr vorgenommen werden wenn $test gleich 1 ist:

    if( $test==1 || $test==2 )

    Wenn die Bedingungen verundet sind, sollte natürlich die unwahrscheinlichste Prüfung an erste Stelle stehen, da es zur Feststellung des Gesamtergebnisses false ja schon genügt wenn ein Teil der Verundung false entspricht (der zweite Teil müsste dann garnicht mehr geprüft werden):

    if( test==2 && test1==1 )

    Wenn in der Bedingung der Rückgabe-Wert von Funktionen ausgewertet wird, sollte falls keine all zu deutliche Wahrscheinlichkeit abzusehen ist, die Prüfung (bei Veroderung) wie folgt aussehen, wenn test2() gegenüber test1() sehr viel Rechenzeit benötigt:

    if( test1() || test2() )

    Denn für den Fall dass test1() dann schon true zurückliefert, muss die aufwendige Funktion test2() ja garnicht mehr aufgerufen werden!
    Das selbe gilt natürlich auch wenn sich die Bedingungen nicht alle innerhalb eines If-Statements befinden:

    function test()
    {
      if( /*wahrscheinlich*/ )
        return 1;
      if( /*unwahrscheinlich*/ )
        return 2;
      // ...
    }

    Wenn die erste Bedingung schon zum Beenden der Funktion führt, muss die zweite Bedingung nicht mehr ausgewertet werden.

  • PHP interne Funktionen nutzen

    Man sollte wenn vorhanden immer PHP interne Funktionen nutzen. Im Beispiel sollen Array-Elemente mit einem Komma getrennt in einem String an einander gehängt werden:

    $string = '';
    $array = array( 'a', 'b', 'c' );
     
    $first = true;
    foreach( $array as $element )
    {
      if( !$first )
        $string .= ',';
      $string .= $element;
      $first = false;
    }
     
    // string enthält jetzt: a,b,c

    Die von PHP zur Verfügung gestellte Funktion implode(…) ist deutlich effizienter und erspart Arbeit:

    $array = array( 'a', 'b', 'c' );
    $string = implode( ',', $array );
    // string enthält jetzt: a,b,c
  • Nicht mehr benötigten Speicher freigeben

    PHP räumt beim Verlassen eines Geltungsbereichs (z.B. beim Verlassen einer Funktion) nicht mehr gültige Variablen automatisch auf. Jedoch gibt es auch eine Möglichkeit Speicher explizit wieder freizugeben, wenn der Inhalt der Variable nichtmehr benötigt wird, aber die Variable noch länger gültig ist. Das empfiehlt sich besonders bei großen, nicht mehr benötigte Arrays oder Objekten die sonst unnötig im Speicher gehalten werden. Den Inhalt einer Variablen explizit freigeben, kann man mit der Funktion unset(…).

  • Daten serialisieren

    Wer z.B. Strings bisher mit einem (zusätzlich auch noch unsicheren) Seperator getrennt an einander gehängt hat um sie irgendwo abzuspeichern und wieder einzulesen:

    // die Daten:
    $daten = array( 'test', 'hudel', 'abc' );
    // die Daten mit Seperator getrennt in einen String schreiben
    $string = implode( '|', $daten ); // string enthält dann: test|hudel|abc
    // die Daten wieder aus dem String auslesen
    $daten = explode( '|', $string ) // das Daten-Array ist anschließend aus dem String wiederhergestellt

    sollte wenn es keinen triftigen Grund dagegen gibt unbdingt die PHP-Funktionen serialize(…) und unserialize(…) verwenden, die nicht nur sicherer (Was passiert wenn sich ein “|” in den Daten befindet?) sondern auch performanter sind:

    $daten = array( 'test', 'hudel', 'abc' );
    $string = serialize( $daten ); // die daten werden in den string serialisiert
    $daten = unserialize( $string ); // das daten-array wird aus dem string wieder hergestellt

    Falls der String anschließend per Query in eine Datenbank geschrieben wird, muss dieser natürlich escaped werden (z.b. mit mysql_escape_string(…))!

  • Suchen in Arrays mit großen Elementen über Hash-Werte

    Wer nur prüfen möchte ob ein Eintrag in einem Array mit großen Elementen (zum Beispiel mehrdimensionalen Arrays) existiert sollte darüber nachdenken ob es sich nicht evtl. lohnt zusätzlich zum Array mit den Daten, ein zweites Array im Speicher zu halten, welches nur Hashwerte der Elemente enthält: Die Suche nach einem kurzen Hash-Key ist bedeutend schneller, als rießen Elemente in foreach-Schleifen durch zu gehen oder mehrmals exterm lange Strings zu vergleichen!

  • Ersetzen in Strings wenn möglich mit String-Funktionen

    Wer Teile eines Strings ersetzen will, sollte wenn möglich die String-Funktionen wie str_replace(…) statt zum Beispiel ereg_replace(…) verwenden, da das Ersetzen über Reguläre Ausrücke natürlich aufwändiger ist.

  • Caching implementieren
    Wer immer wieder das gleiche berechnet, kann es evtl auch cachen. Mit Smarty zum Beispiel kann man seine Ausgaben automatisch cachen lassen.
    Wer fertig berechnete Daten cachen möchte, weil es z.B. genügt wenn diese alle X Stunden aktuell angezeigt werden, serialisiert diese Daten am besten wie oben beschrieben und kann diese dann zum Beispiel zusammen mit dem TimeStamp in die Datenbank speichern. Bei der nächsten anfrage werden dann nurnoch die fertig berechneten Ergebnisse aus der Datenbank geladen wenn der TimeStamp noch ausreicht, und deserialisiert.
    Und dann kann man ja auch noch Bilder mit PHP generieren: Wenn sich das Bild nicht bei jedem Aufruf ändert sondern immer gleich bleibt, kann man dieses Bild beim ersten mal einfach auch fertig ins Dateisystem abspeichern. Wird das Bild das zweite mal angefragt wird geprüft ob die Datei schon im Cache existiert und wenn ja wird diese Datei ausgegeben. Beim Ausgeben der gecachten Daten ist folgendes zu beachten: Falls noch auszuführender Code im Script nach der Ausgabe des Bilds existiert (z.B. Statistikeintrag schreiben) sollte dieser wenn möglich vor die Ausgabe verschoben werden. Dann können bevor das Bild eingelesen und ausgeben wird alle Datenbankverbindungen geschlossen und nicht mehr benötigte Daten freigeben werden. So benötigt das Script während die Anzeige erstellt wird keinen unnötigen Speicher mehr.
    Egal fertige Dateien oder Bilder nur noch zum Browser weitergeben werden sollen, es ist wichtig dies richtig zu implementieren: Lesen sie nicht erst die komplette Datei ein um sie dann wieder auszugeben, das ist überflüssig, und könnte evtl mehr Speicher verbrauchen als das neuerstellen des Bilds. Mit der Funktion readfile(…) kann die Datei direkt in die Ausgabe geschrieben werden, das spart unnötigen Speicherverbrauch und ist auch schneller.

  • Ausgabe-Pufferung vermeiden

    Wer die Ausgabe sinnlos puffert (zum Beispiel mit ob_start(…) usw) verbrät unnötig Speicher und Ladezeit. Wenn die Ausgabe sofort erfolgt, muss diese nicht erst im Speicher zwischengespeichert werden. Die bereits erzeugte Ausgabe kann dann auch schon parallel zum Client übertragen werden während das PHP-Script noch weiter ausgeführt wird.

  • Datenbank-Abfragen optimieren

    Wenn mit Datenbank-Abfragen gearbeitet wird, sollte man natürlich auch diese möglichst performant gestalten:
    Optimierung von Datenbankabfragen

Weitere Tipps zur Performance-Optimierung gibts hier:
Stichworte Performance und Optimierung
Und Buch-Empfehlungen von mir hier:
PHP Bücher

Eine gute Seite zum Thema (auf Englisch):
A HOWTO on Optimizing PHP


Weiterlesen


Kategorien: Entwicklung

Stichwörter: , , , , ,

Verwandte Beiträge:

3 Kommentare zu “Tipps zur Performance Optimierungen von PHP-Scripten im Kleinen”

  1. Von paul

    hallo,

    schön das sich jemand mal die mühe gemacht hat ein paar tipps zusammen zutragen. man könnte den tipp “Keine unnötigen Funktionsaufrufe in Abbruchbedingungen von Schleifen” auch gleich im tipp “Über Arrays mit großen Elementen iterieren ohne zu kopieren” verwenden ;)

      foreach( array_keys($array) as $elementkey ) // array_keys(...) gibt ein Array mit den Keys ohne Daten zurück

    Antwort: Der Aufruf von array_keys(…) sieht im Beispiel unperformant aus, ich habe es geändert, danke!
    Jedoch sieht es nur so aus: Der Aufruf von array_keys(…) befindet sich in keiner Abbruchbedingung. Zu einer foreach-Schleife gehören nur die Variable in die das aktuelle Element gespeichert wird, und eine Liste mit Elementen. Abgebrochen wird, wenn alle Elemente des Arrays abgearbeitet wurden. Die Funktion array_keys(…) wird wie folgendes Beispiel zeigt, nur einmal ausgeführt. Dann wird mit dem zurückgegebenen Array gearbeitet:

    <?php
     
      function test() {
        print( 'X' );
        return array( '1', '2', '3' );
      }
     
      foreach( test() as $element )
        print( $element );
     
    ?>

    Ausgabe:

      X123

    Dennoch Danke für den Hinweis, so ist das Beispiel jetzt besser.

  2. Von paul

    wie wahr, würde die funktion mehrfach ausgeführt währe der zeiger wieder auf dem ersten element des arrays…

  3. Von Johannes

    Sehr hilfreich, danke!

Die Kommentare dieser Seite können über folgendes Feed verfolgt werden: Kommentar-Feed dieser Seite