Gibt es einen Leistungsunterschied zwischen i++
und ++i
wenn der resultierende Wert nicht verwendet wird?
c
performance
optimization
post-increment
pre-increment
Mark Harrison
quelle
quelle
Antworten:
Zusammenfassung: Nein.
i++
könnte möglicherweise langsamer sein als++i
, da der alte Wert voni
möglicherweise für die spätere Verwendung gespeichert werden muss, aber in der Praxis werden alle modernen Compiler dies wegoptimieren.Wir können dies demonstrieren, indem wir uns den Code für diese Funktion ansehen, sowohl mit
++i
als auchi++
.Die Dateien sind die gleichen, mit Ausnahme
++i
undi++
:Wir werden sie kompilieren und auch den generierten Assembler erhalten:
Und wir können sehen, dass sowohl das generierte Objekt als auch die Assembler-Dateien identisch sind.
quelle
++i
anstelle von immer noch eine gute Vorgehensweise isti++
. Es gibt absolut keinen Grund, dies nicht zu tun, und wenn Ihre Software jemals eine Toolchain durchläuft, die sie nicht optimiert, ist Ihre Software effizienter. Da es genauso einfach zu tippen ist++i
wie zu tippeni++
, gibt es wirklich keine Entschuldigung, es überhaupt nicht zu verwenden++i
.Aus Effizienz versus Absicht von Andrew Koenig:
Und :
Wenn der resultierende Wert nicht verwendet wird, würde ich verwenden
++i
. Aber nicht, weil es effizienter ist: weil es meine Absicht richtig ausdrückt.quelle
i++
der gleichen Art und Weise würde ich Codei += n
oderi = i + n
, dh in Form Ziel Verb - Objekt , mit dem Zieloperanden nach links des Verbs Operator. Im Fall voni++
gibt es kein rechtes Objekt , aber die Regel gilt weiterhin, wobei das Ziel links vom Verboperator bleibt .Eine bessere Antwort ist, dass dies
++i
manchmal schneller, aber niemals langsamer ist.Jeder scheint davon auszugehen, dass
i
es sich um einen regulären eingebauten Typ handelt, wie zint
. In diesem Fall gibt es keinen messbaren Unterschied.Wenn
i
es sich jedoch um einen komplexen Typ handelt, können Sie durchaus einen messbaren Unterschied feststellen. Denni++
Sie müssen eine Kopie Ihrer Klasse erstellen, bevor Sie sie erhöhen. Abhängig davon, was in einer Kopie enthalten ist, kann es tatsächlich langsamer sein, da++it
Sie mit nur den endgültigen Wert zurückgeben können.Ein weiterer Unterschied besteht darin, dass
++i
Sie die Möglichkeit haben, anstelle eines Werts eine Referenz zurückzugeben. Je nachdem, was beim Erstellen einer Kopie Ihres Objekts erforderlich ist, kann dies wiederum langsamer sein.Ein reales Beispiel dafür, wo dies auftreten kann, wäre die Verwendung von Iteratoren. Das Kopieren eines Iterators ist in Ihrer Anwendung wahrscheinlich kein Flaschenhals, aber es ist immer noch eine gute Praxis, sich daran zu gewöhnen, ihn zu verwenden,
++i
anstatt dort,i++
wo das Ergebnis nicht beeinflusst wird.quelle
Kurze Antwort:
Es gibt nie einen Unterschied zwischen
i++
und++i
in Bezug auf die Geschwindigkeit. Ein guter Compiler sollte in beiden Fällen keinen unterschiedlichen Code generieren.Lange Antwort:
Was jede andere Antwort nicht erwähnt, ist, dass der Unterschied zwischen
++i
versusi++
nur innerhalb des Ausdrucks, den er findet, Sinn macht.Im Fall von
for(i=0; i<n; i++)
ist dasi++
allein in seinem eigenen Ausdruck: Es gibt einen Sequenzpunkt vor demi++
und einen nach ihm. Somit ist der einzige generierte Maschinencode "Erhöheni
um1
" und es ist genau definiert, wie dieser in Bezug auf den Rest des Programms sequenziert wird. Wenn Sie es also in ein Präfix ändern würden++
, wäre es nicht im geringsten wichtig, Sie würden immer noch nur den Maschinencode "erhöheni
um1
" erhalten.Die Unterschiede zwischen
++i
undi++
nur in Ausdrücken wiearray[i++] = x;
versusarray[++i] = x;
. Einige mögen argumentieren und sagen, dass der Postfix bei solchen Operationen langsamer sein wird, weil das Register, in dem sich das Postfixi
befindet, später neu geladen werden muss. Beachten Sie jedoch, dass der Compiler Ihre Anweisungen nach Belieben bestellen kann, solange er nicht "das Verhalten der abstrakten Maschine unterbricht", wie es der C-Standard nennt.Sie können also davon ausgehen, dass dies
array[i++] = x;
in Maschinencode übersetzt wird als:i
in Register A.i
in Register A // ineffizient, da zusätzliche Anweisungen hier bereits einmal ausgeführt wurden.i
.Der Compiler kann den Code genauso gut effizienter erstellen, z.
i
in Register A.i
.Nur weil Sie als C-Programmierer darauf trainiert sind, zu glauben, dass der Postfix
++
am Ende auftritt , muss der Maschinencode nicht auf diese Weise bestellt werden.Es gibt also keinen Unterschied zwischen Präfix und Postfix
++
in C. Nun, was Sie als C-Programmierer unterscheiden sollten, sind Personen, die in einigen Fällen uneinheitlich Präfix und in anderen Fällen Postfix verwenden, ohne Begründung warum. Dies deutet darauf hin, dass sie sich nicht sicher sind, wie C funktioniert, oder dass sie falsche Sprachkenntnisse haben. Dies ist immer ein schlechtes Zeichen, was wiederum darauf hindeutet, dass sie andere fragwürdige Entscheidungen in ihrem Programm treffen, die auf Aberglauben oder "religiösen Dogmen" beruhen."Präfix
++
ist immer schneller" ist in der Tat ein solches falsches Dogma, das unter angehenden C-Programmierern häufig vorkommt.quelle
Ein Blatt von Scott Meyers nehmen, effektiver c ++ Punkt 6: Unterscheiden Sie zwischen Präfix- und Postfix-Formen von Inkrementierungs- und Dekrementierungsoperationen .
Die Präfixversion wird in Bezug auf Objekte, insbesondere in Bezug auf Iteratoren, immer dem Postfix vorgezogen.
Der Grund dafür, wenn Sie sich das Anrufmuster der Operatoren ansehen.
In diesem Beispiel ist leicht zu erkennen, dass der Präfixoperator immer effizienter ist als der Postfix. Wegen der Notwendigkeit eines temporären Objekts bei der Verwendung des Postfixes.
Wenn Sie Beispiele mit Iteratoren sehen, verwenden diese daher immer die Präfixversion.
Aber wie Sie für int's betonen, gibt es praktisch keinen Unterschied aufgrund der Compiler-Optimierung, die stattfinden kann.
quelle
Hier ist eine zusätzliche Beobachtung, wenn Sie sich Sorgen über die Mikrooptimierung machen. Das Dekrementieren von Schleifen kann "möglicherweise" effizienter sein als das Inkrementieren von Schleifen (abhängig von der Befehlssatzarchitektur, z. B. ARM), vorausgesetzt:
In jeder Schleife haben Sie jeweils eine Anweisung für:
1
zui
.i
kleiner als a ist100
.i
kleiner als a ist100
.Während eine Dekrementierungsschleife:
Die Schleife enthält eine Anweisung für:
i
, Setzen des CPU-Registerstatusflags.Z==0
).Dies funktioniert natürlich nur beim Dekrementieren auf Null!
Erinnert an das ARM System Developer's Guide.
quelle
Bitte lassen Sie nicht zu, dass die Frage "welches schneller ist" der entscheidende Faktor für die Verwendung ist. Es besteht die Möglichkeit, dass Sie sich nie so sehr darum kümmern werden, und außerdem ist die Lesezeit für Programmierer weitaus teurer als die Maschinenzeit.
Verwenden Sie, was für den Menschen, der den Code liest, am sinnvollsten ist.
quelle
Zuallererst: Der Unterschied zwischen
i++
und++i
ist in C vernachlässigbar.Zu den Details.
1. Das bekannte C ++ - Problem:
++i
ist schnellerIn C ++
++i
ist es effizienter, wenn fi
eine Art Objekt mit einem überladenen Inkrementoperator ist.Warum?
In
++i
wird das Objekt zuerst inkrementiert und kann anschließend als konstante Referenz an eine andere Funktion übergeben werden. Dies ist nicht möglich, wenn der Ausdruck lautet,foo(i++)
weil jetzt das Inkrement ausgeführt werden muss, bevorfoo()
es aufgerufen wird, aber der alte Wert an übergeben werden mussfoo()
. Folglich muss der Compiler eine Kopiei
davon erstellen, bevor er den Inkrementoperator für das Original ausführt. Die zusätzlichen Konstruktor- / Destruktoraufrufe sind der schlechte Teil.Wie oben erwähnt, gilt dies nicht für grundlegende Typen.
2. Die wenig bekannte Tatsache:
i++
kann schneller seinWenn kein Konstruktor / Destruktor aufgerufen werden muss, was in C immer der Fall ist
++i
undi++
gleich schnell sein sollte, oder? Nein. Sie sind praktisch gleich schnell, aber es kann kleine Unterschiede geben, die die meisten anderen Antwortenden falsch verstanden haben.Wie kann
i++
schneller sein?Der Punkt sind Datenabhängigkeiten. Wenn der Wert aus dem Speicher geladen werden muss, müssen zwei nachfolgende Vorgänge damit ausgeführt, inkrementiert und verwendet werden. Mit
++i
muss die Inkrementierung durchgeführt werden, bevor der Wert verwendet werden kann. Beii++
hängt die Verwendung nicht vom Inkrement ab, und die CPU kann die Verwendungsoperation parallel zur Inkrementoperation ausführen. Der Unterschied ist höchstens ein CPU-Zyklus, also ist er wirklich vernachlässigbar, aber er ist da. Und es ist umgekehrt, als viele erwarten würden.quelle
++i
oderi++
in einem anderen Ausdruck verwendet wird, ändert ein Wechsel zwischen ihnen die Semantik des Ausdrucks, sodass ein möglicher Leistungsgewinn / -verlust nicht in Frage kommt. Wenn sie eigenständig sind, dh das Ergebnis der Operation nicht sofort verwendet wird, würde jeder anständige Compiler sie auf dieselbe Weise kompilieren, beispielsweise auf eineINC
Assembly-Anweisung.i++
und++i
kann durch einen durch das Einstellen Schleifenkonstanten austauschbar in fast jedem möglichen Situation verwendet werden, so dass sie in der Nähe gleichwertig sind , was sie tun , für den Programmierer. 2) Obwohl beide nach demselben Befehl kompilieren, unterscheidet sich ihre Ausführung für die CPU. Im Fall voni++
kann die CPU das Inkrement parallel zu einem anderen Befehl berechnen , der denselben Wert verwendet (CPUs tun dies wirklich!), Während++i
die CPU den anderen Befehl nach dem Inkrement planen muss .if(++foo == 7) bar();
undif(foo++ == 6) bar();
sind funktional gleichwertig. Der zweite kann jedoch einen Zyklus schneller sein, da der Vergleich und das Inkrement von der CPU parallel berechnet werden können. Nicht dass dieser einzelne Zyklus viel ausmacht, aber der Unterschied ist da.<
zum Beispiel vs<=
), wo sie++
normalerweise verwendet werden, so dass die Konvertierung zwischen den obwohl oft leicht möglich ist.@Mark Obwohl der Compiler die (stapelbasierte) temporäre Kopie der Variablen optimieren darf und gcc (in neueren Versionen) dies tut, bedeutet dies nicht, dass dies immer alle Compiler tun werden.
Ich habe es gerade mit den Compilern getestet, die wir in unserem aktuellen Projekt verwenden, und 3 von 4 optimieren es nicht.
Gehen Sie niemals davon aus, dass der Compiler es richtig macht, besonders wenn der möglicherweise schnellere, aber niemals langsamere Code so einfach zu lesen ist.
Wenn Sie keine wirklich dumme Implementierung eines der Operatoren in Ihrem Code haben:
Alwas bevorzugen ++ i gegenüber i ++.
quelle
In C kann der Compiler sie im Allgemeinen so optimieren, dass sie gleich sind, wenn das Ergebnis nicht verwendet wird.
In C ++ ist die Präfixversion jedoch wahrscheinlich schneller als die Postfix-Version, wenn andere Typen verwendet werden, die ihre eigenen ++ - Operatoren bereitstellen. Wenn Sie die Postfix-Semantik nicht benötigen, ist es besser, den Präfix-Operator zu verwenden.
quelle
Ich kann mir eine Situation vorstellen, in der Postfix langsamer ist als Präfixinkrement:
Stellen Sie sich vor, ein Prozessor mit Register
A
wird als Akkumulator verwendet und ist das einzige Register, das in vielen Anweisungen verwendet wird (einige kleine Mikrocontroller sind tatsächlich so).Stellen Sie sich nun das folgende Programm und dessen Übersetzung in eine hypothetische Versammlung vor:
Präfixinkrement:
Postfix-Inkrement:
Beachten Sie, wie der Wert von
b
neu geladen werden musste. Mit dem Präfix-Inkrement kann der Compiler den Wert einfach inkrementieren und ihn weiter verwenden. Vermeiden Sie möglicherweise ein erneutes Laden, da der gewünschte Wert nach dem Inkrement bereits im Register vorhanden ist. Beim Postfix-Inkrement muss der Compiler jedoch zwei Werte verarbeiten, einen alten und einen inkrementierten Wert, was, wie oben gezeigt, zu einem weiteren Speicherzugriff führt.Wenn der Wert des Inkrements nicht verwendet wird, wie z. B. eine einzelne
i++;
Anweisung, kann (und tut) der Compiler natürlich einfach eine Inkrementanweisung generieren, unabhängig von der Verwendung von Postfix oder Präfix.Als Randnotiz möchte ich erwähnen, dass ein Ausdruck, in dem es ein
b++
gibt, nicht einfach++b
ohne zusätzlichen Aufwand in einen Ausdruck konvertiert werden kann (zum Beispiel durch Hinzufügen von a- 1
). Ein Vergleich der beiden, wenn sie Teil eines Ausdrucks sind, ist also nicht wirklich gültig. Wenn Sieb++
einen Ausdruck verwenden, den Sie nicht verwenden können++b
, ist dies häufig++b
falsch , selbst wenn er möglicherweise effizienter wäre. Eine Ausnahme ist natürlich, wenn der Ausdrucka = b++ + 1;
darum bittet (zum Beispiel, der geändert werden kanna = ++b;
).quelle
Ich habe hier durch die meisten Antworten gelesen , und viele der Kommentare, und ich habe keinen Hinweis auf die sehen ein Beispiel , dass ich denken könnte , wo
i++
ist effizienter als++i
(und vielleicht überraschend--i
war effizienter alsi--
). Das ist für C-Compiler für den DEC PDP-11!Der PDP-11 hatte Montageanweisungen zum Vordekrementieren eines Registers und zum Nachinkrementieren, aber nicht umgekehrt. Die Anweisungen ermöglichten die Verwendung eines "Allzweck" -Registers als Stapelzeiger. Wenn Sie also so etwas verwenden, kann
*(i++)
es zu einer einzigen Assembly-Anweisung kompiliert werden, während*(++i)
dies nicht möglich ist.Das ist natürlich ein sehr esoterisch Beispiel, aber es bietet die Ausnahme in dem Post-Inkrement effizienter ist (oder sollte ich sagen war , da es nicht viel Nachfrage nach PDP-11 - C - Code ist in diesen Tagen).
quelle
--i
und verwendeni++
.Ich bevorzuge jedoch immer das Vorinkrementieren ...
Ich wollte darauf hinweisen, dass der Compiler selbst beim Aufrufen der Operator ++ - Funktion das Temporäre optimieren kann, wenn die Funktion inline wird. Da der Operator ++ normalerweise kurz ist und häufig im Header implementiert wird, wird er wahrscheinlich inline.
Aus praktischen Gründen gibt es wahrscheinlich keinen großen Unterschied zwischen der Leistung der beiden Formen. Ich bevorzuge jedoch immer die Vorinkrementierung, da es besser erscheint, direkt auszudrücken, was ich zu sagen versuche, als mich auf den Optimierer zu verlassen, um dies herauszufinden.
Wenn Sie dem Optmizer weniger Zeit geben, wird der Compiler wahrscheinlich schneller ausgeführt.
quelle
Mein C ist etwas rostig, deshalb entschuldige ich mich im Voraus. Schnell kann ich die Ergebnisse verstehen. Ich bin jedoch verwirrt darüber, wie beide Dateien zu demselben MD5-Hash kamen. Vielleicht läuft eine for-Schleife gleich, aber würden die folgenden 2 Codezeilen nicht unterschiedliche Assemblys generieren?
vs.
Der erste schreibt den Wert in das Array und erhöht dann i. Die zweiten Inkremente schreibe ich dann in das Array. Ich bin kein Assembly-Experte, aber ich sehe nur nicht, wie dieselbe ausführbare Datei von diesen zwei verschiedenen Codezeilen generiert wird.
Nur meine zwei Cent.
quelle
foo[i++]
auf,foo[++i]
ohne etwas anderes zu ändern, würde offensichtlich die Programmsemantik ändern, aber auf einigen Prozessoren wäre es schneller, wenn Sie einen Compiler ohne eine Optimierungslogik zum Anheben von Schleifen verwenden, inkrementierenp
undq
einmal ausführen und dann eine Schleife ausführen, die z. B.*(p++)=*(q++);
eine Schleife ausführt, die ausgeführt wird*(++pp)=*(++q);
. Bei sehr engen Schleifen auf einigen Prozessoren kann der Geschwindigkeitsunterschied erheblich sein (mehr als 10%), aber dies ist wahrscheinlich der einzige Fall in C, in dem das Nachinkrement wesentlich schneller ist als das Vorinkrement.