
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.
Schlagwörter: PHP, postfix, präfix, schleife













Christoph sagt
am 8. Oktober 2007 @ 13:32
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
admin sagt
am 8. Oktober 2007 @ 13:42
Sorry. Nun darf sich Dein Ego wieder freuen – vor allem auf die Kommentare, die hier gleich zum Beitrag kommen
grübelgrobi sagt
am 8. Oktober 2007 @ 15:49
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
admin sagt
am 8. Oktober 2007 @ 15:53
Da ist was dran. Warum nur hab ich das Duden-Fremdwörterlexikon nicht daheim???
Christoph sagt
am 8. Oktober 2007 @ 17:10
Es heißt echt nicht "Postfix"? Echt? Entweder hab ich alle Bücher falsch in Erinnerung oder alle Bücher waren falsch…
admin sagt
am 8. Oktober 2007 @ 17:26
Es heißt wohl doch Postfix. Man vergleiche die Anzahl der Ergebnisse:
http://www.google.de/search?hl=de&q=postfix+inkrement&meta=
http://www.google.de/search?hl=de&q=suffix+inkrement&meta=
GhostGambler sagt
am 8. Oktober 2007 @ 22:39
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.
Michael sagt
am 9. Oktober 2007 @ 09:54
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:
admin sagt
am 9. Oktober 2007 @ 10:02
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.
Stefan sagt
am 10. Oktober 2007 @ 22:30
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.
admin sagt
am 11. Oktober 2007 @ 08:26
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
Stefan sagt
am 11. Oktober 2007 @ 17:37
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€.
Leif sagt
am 12. Oktober 2007 @ 13:23
"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.
Luris sagt
am 12. Oktober 2007 @ 15:21
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.
Leif sagt
am 15. Oktober 2007 @ 08:36
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-.
admin sagt
am 15. Oktober 2007 @ 08:42
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…
Leif sagt
am 15. Oktober 2007 @ 09:07
Oh, ich seh's gerade! LOL.
Sammie sagt
am 15. Oktober 2007 @ 16:48
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.
admin sagt
am 15. Oktober 2007 @ 17:31
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.
Leif sagt
am 16. Oktober 2007 @ 08:26
Noch als Ergänzung:
Die folgende Variante wurde nicht genannt:
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.