Postfix- vs. Präfix-Inkrementierung

Diesmal wollen wir die alte Frage, ob nun das Postfix- oder das Präfix-Inkrement schneller ist, von der C++-Welt auf PHP übertragen und kurz messen, welche Variante wir in unseren for-Schleifen nehmen sollten.

Dass das Inkrement von Haus aus schon eine eher anspruchslose Operation für den Prozessor darstellt, lassen wir es gleich 100.000.000 mal durchführen. Zum Vergleich testen wir auch noch die längeren Varianten $i += 1 und $i = $i + 1.

Das Testscript sieht folgendermaßen aus:

$before = microtime(true);
 
for ( $i = 0; $i < 100000000; /* Hier jeweils das Inkrement. */ ) {
}
 
$duration = microtime(true) - $before;
 
echo $duration;

Nach dem Durchlauf erhalten wir folgende Ergebnisse:

Typ Gesamtlaufzeit durchschnittliche Laufzeit pro Durchlauf Verhältnis zur schnellsten Variante
Präfix 22,1 s 221 ns 100 %
Kombinierter Operator 24,6 s 246 ns 112 % (+ 12%)
Postfix 25,5 s 255 ns 116 % (+ 16%)
Lange Version 28,7 s 287 ns 130 % (+ 30%)

Wir sehen also, dass der minimale Unterschied in den Operatoren sich aus der C/C++-Welt auch hier zeigt. Die lange Version ist weit abgeschlagen (und daher sowohl aus Performance- als auch aus ästhetischen Gründen nicht empfehlenswert), während der kombinierte Operator sich erstaunlich gut schlägt und mit dem Postfix-Operator quasi gleichauf liegt.
Das Präfix-Inkrement liegt mit einem Abstand von 2,5 Sekunden mit einem ausreichendem Abstand vorn, um sagen zu können, dass es einen messbaren Unterschied gibt und PHP intern die Varianten nicht gleich behandelt.

Da der Unterschied aber nur im quasi nicht messbaren Bereich liegt (Präfix ist um 35 ns schneller als Postfix), fällt der Zeitverlust nur bei Schleifen ins „Gewicht“, die wirklich viele Millionen male durchlaufen werden müssen. Und ob bei so häufigen Durchläufen die Zeit für das Inkrement noch eine Rolle spielt, ist dann auch noch von Fall zu Fall zu entscheiden.
Auf jeden Fall kann man mit dem Präfix-Inkrement nichts falsch machen und fährt immer gut.

Hintergrund: Der Postfix-Operator gibt erst den Wert zurück und erhöht ihn danach. Daher benötigt er einen temporären Zwischenspeicher für den Wert vor dem Inkrement. Das Anlegen der Variable benötigt natürlich etwas Zeit. Da das Präfix-Inkrement erst inkrementiert und dann den Wert zurückgibt, benötigt man hier keine temporäre Variable und damit fällt auch der Zeitbedarf für das Allokieren des Speichers weg.
Zwei Beispiel-Implementierungen (aus dem wundervollen Buch „C/C++ Kompendium“ von Dirk Louis) zeigen die Unterschiede an einer fiktiven und an sich auch nutzlosen Klasse „MyInteger“:

class MyInteger {
public:
    int wert;
 
    MyInteger(int i) { wert = i; }
    MyInteger() { wert = 0; }
 
    // Postfix-Inkrement
    const MyInteger operator ++ (int) {
        MyInteger tmp = *this;
        this->wert += 1;
        return tmp;
    }
 
    // Präfix-Inkrement
    MyInteger& operator ++ () {
        this->wert += 1;
        return *this;
    }
}

Halten wir also fest: Immer das Präfix-Inkrement verwenden. Sieht schön aus und ist schnell.

Wie immer gibt es die verwendeten Scripts sowie die Ergebnisse zum Download.

Dieser Beitrag wurde uns freundlichst von Christoph Mewes zur Verfügung gestellt. Vielen Dank.

Jan hat 152 Beiträge geschrieben

20 Kommentare zu “Postfix- vs. Präfix-Inkrementierung

  1. Christoph sagt:

    Nanu, diesmal kein Backlink oder Erwähnung meines Namens? Nur, weil ich nicht allein posten will, um der Verantwortung zu entgehen, heißt das nicht, dass mein Ego auf diesen Schub verzichten kann :p

  2. grübelgrobi sagt:

    Müsste es nicht heißen „Suffix- vs. Präfix-Inkrementierung“ oder wollt Ihr in google nicht nur unter php-performance sondern auch über postfix-performance gefunden werden?

    jmtc 😉

  3. GhostGambler sagt:

    Jeder etwas bessere PHP-Cache wandelt solch Dinge automatisch um, demnach ist das in der „Produktion“ ziemlich egal~

    Also auf Wikipedia ist Postfix als Alternative zu Suffix in der Mathematik und Informatik vermerkt. Aber in Anbetracht, dass die Duden-Suche genau 0 Treffer hat, würde ich Suffix als Bezeichner bevorzugen.

  4. Michael sagt:

    Was für ein Tipp: „Immer die Präfix Notation verwenden“… Es gibt bestimmt _nie_ Fälle, wo man es genau andersrum braucht… Der DAU wird sich bedanken :rolleyes:

  5. admin sagt:

    Es mag Fälle geben, aber ich glaube nicht, dass der DAU solche Konstrukte wie $array[$i++] gebrauchen wird. Die Zielgruppe meines Blogs sind auch weniger Menschen, die mal eben ein PHP-Buch durchgeblättert haben, sondern eher die, die etwas mit den hier genannten Informationen anfangen und sie in vorhandenes Wissen einbauen kann.

    PS: Die Titel-Diskussion möchte ich an dieser Stelle beenden. Anscheinend geht beides und je nach Quelle wird auch das eine oder andere bevorzugt. Wir wissen ja trotzdem alle ,was gemeint ist und damit ist der Zweck der Überschrift erfüllt.

  6. Stefan sagt:

    Also mal ehrlich: der Vergleich ist doch insgesamt ziemlich lächerlich, betrachtet man mal das Verhältnis zwischen Zeitersparnis durch derartige „Performance-Tipps“ und Zeitersparnis durch ’n Konzept mit Hirn oder Beachtung einiger Grundregeln das Laufzeitverhalten betreffend.

    Ok, klar: die Sache anstelle von $i = $i + 1 einen der beiden Präfix- bzw. Postfix-Operatoren zu benutzen ist ’n sinnvoller Tipp. Allerdings sollte das auch so einigermaßen klar sein – schließlich sind letztere eigene Operatoren (!) und nicht (zusammengesetzte) Anweisungen.

    Und um den Einwand gleich vorweg zu nehmen: nein, ich kann auch nicht so richtig dicke programmieren. Aber ich find es schon einigermaßen albern, sich über Geschwindigkeitsvorteile von solchen Trivias zu unterhalten.

    Nix für ungut.
    Cheers.

  7. admin sagt:

    Sicherlich kann man mit diesem Tipp eine Schnecke nicht in ein Rennpferd verwandeln, aber trotzdem ist es interessant, die Hintergründe zu erforschen. Es geht mir nicht allein darum, dass etwas schneller als das andere ist sondern auch wieso.

    Und außerdem: Einige hier angesprochene Tipps sind für sehr große Seiten von Nutzen, wo es durchaus sinnvoll ist, mit solchen „trivialen Tipps“ zu arbeiten.
    Für alle anderen Seiten: Wir haben ja nicht nur Themen zu Benchmarks sondern auch zu Cache-Techniken oder Datenbankstrukturen. Und wenn man da 80% Traffic einspart (durch Caching) oder eine SQL-Abfrage um einige Sekunden beschleunigen kann, ist das nicht mehr „trivial“ (ja, das Wort hats mir angetan 😉 )

  8. Stefan sagt:

    Ich wollte keinesfalls das Anliegen der Seite als solcher untergraben (ich hab hier auch sehr, sehr brauchbare Sachen gelesen!) – vielmehr dieses einen Eintrags.

    Zumal, das füg ich hier noch hinzu, die Messungen ziemlich wenig aussagen in diesem Kontext: Weder steht irgendwo, wie oft der Test wiederholt und woraus dann das Mittel gewonnen wurde (der Text suggeriert sogar, es sei alles nur einmal gelaufen) noch wird darauf eingegangen, dass z.B. allein (Hintergrund-)Prozesse jedmöglicher Art darauf sehr großen Einfluss haben können. Ok, die Formulierung: „im quasi nicht messbaren Bereich“ macht hier einiges wieder gut – aber nur um dann gleich wieder auf die Anzahl der Schleifendurchläufe anzuspielen, die hier 100 000 000 000 (in Worten: „einhundert Millionen“) beträgt. Wer einhundert Millionen Durchläufe machen muss in seiner Applikation, der wird sich kaum um „$i++“ oder „++$i“ scheren …

    Und wenn er wirklich glaubt, hier ordentlich optimieren zu können, dann ist er entweder ein absolut genialer Entwickler (denn alles sonst muss dann schon höchst performant sein) oder aber ein dilletantischer Kleinkram-Fetischist 🙂

    Just my 0,02€.

  9. Leif sagt:

    „Wer einhundert Millionen Durchläufe machen muss in seiner Applikation, der wird sich kaum um “$i++” oder “++$i” scheren“

    Ich würde es schon aus Gründen der Unsinnigkeit überprüfen. Wenn ich den temporären vorherigen Wert der Variable nicht brauche, dann werde ich $i++ auch nicht nehmen. Ich lasse meine Applikation eben nur das machen, was nötig ist. Das kommt noch vor der Optimierung, WIE sie es macht.
    Dieses Topic erweckt den Anschein, dass es eine reine Performancefrage behandeln würde. Aber das ist es eben nicht, da sich die Varianten unterschiedlich verhalten. Und nach diesem Verhalten richtet sich der zielgerichtete Programmierer in erster Linie.

  10. Luris sagt:

    Da ich persöhnlich den ganzen Vergleich für enormen unsinn halte, möchte ich dennoch auch ein paar Beispiele zum besten geben:

    //Beispiel 1: Schleife umdrehen um dem Parser einen Nullwert-vergleich zu ermöglichen (ist für die Maschine erheblich einfacher)
    $before = microtime(true);
    for ( $i = 100000000; $i– != 0 ; );
    echo (microtime(true) – $before) . „“;

    //Beispiel 2: Wir ermöglichen dem Parser Typ-Casts zu ignorieren
    $before = microtime(true);
    for ( $i = 100000000; $i– !== 0 ; );
    echo (microtime(true) – $before) . „“;

    //Beispiel 3: Der Parser erhält eine verkürzte Anweisung (geringerer Parsingaufwand)
    $before = microtime(true);
    for ( $i = 100000000; $i–; );
    echo (microtime(true) – $before) . „“;

    Wie den meisten auffallen dürfte, lässt sich auf diese Art noch ein bisschen mehr an Geschwindigkeit erreichen. Das liegt aber nicht wie der Autor meint am Zwischenspeichern von Werten, das wäre vollkommen nebensächlich. Der ware Grund ist im Parser zu suchen, ein vernünftiger Cache, sollte das ganze aber im Normalfall soweit ignorieren, dass es egal ist wie man vorgeht.

  11. Leif sagt:

    Sehr Interessant. Ich glaube allerdings, dass du Parser und Interpreter nicht ganz auseinanderhältst.
    Beispiel 2 verwundert mich, denn die Zahl 0 ist ein Integer, muss also ohnehin nicht gecastet werden, was nach dem Parsing ja schon feststeht. Vielmehr führt das !== dazu, dass der Typ trotzdem überprüft wird. Außer, das wird optimiert. Kann gut sein, dass ich mich irre, was meinst du?
    BTW, besser $i– als $i-. 😉

  12. admin sagt:

    zu dem (minus)(minus): Leider wandelt WordPress das in einen Gedankenstrich um, während ein einfaches Minus ein Bindestrich ist. Für 2 Minusse müsste man deshalb 3 oder 4 Minusse schreiben. WordPress ist da leider nicht für einen Programmierblog geeignet…

  13. Sammie sagt:

    Kann mir nochmal jemand erkären, wo jetzt genau der Unterschied zwischen “$i++” und “++$i” liegt? Wann ist es besser welche Variante zu verwenden? Verstehe noch nicht so ganz, wie sich die beiden Varianten letztendlich unterscheiden.

  14. admin sagt:

    Also wenn Du den aktuellen Wert von $i nicht benötigst, nutzt Du ++$i.
    Wenn Du ihn benötigst (eher selten der Fall), nimmst Du $i++

    $i++ gibt zuerst den aktuellen Wert von $i zurück und inkrementiert anschließend.
    ++$i erhöht den Wert von $i direkt ohne vorherige Rückgabe.

  15. Leif sagt:

    Noch als Ergänzung:

    Die folgende Variante wurde nicht genannt:

    for($i = 0; ++$i < 1000;)
    {
       //LOL
    }

    Ist aber auch nicht so gut. Der letzte Vorschlag von Luris ist den anderen Varianten weit überlegen (wenn auch nicht wegen des Parsings). Aber dann muss man eben rückwärts durch, was nicht immer geht.

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>