Gibt es einen Leistungsunterschied zwischen i++
und ++i
wenn der resultierende Wert nicht verwendet wird?
Gibt es einen Leistungsunterschied zwischen i++
und ++i
wenn der resultierende Wert nicht verwendet wird?
Antworten:
Zusammenfassung: Nein.
i++
könnte möglicherweise langsamer sein als ++i
, da der alte Wert von i
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 auch i++
.
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
Die Dateien sind die gleichen, mit Ausnahme ++i
und i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
Wir werden sie kompilieren und auch den generierten Assembler erhalten:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
Und wir können sehen, dass sowohl das generierte Objekt als auch die Assembler-Dateien identisch sind.
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
anstelle von immer noch eine gute Vorgehensweise ist i++
. 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 tippen i++
, gibt es wirklich keine Entschuldigung, es überhaupt nicht zu verwenden ++i
.
Aus Effizienz versus Absicht von Andrew Koenig:
Erstens ist es alles
++i
andere als offensichtlich, dass dies effizienter ist alsi++
zumindest bei ganzzahligen Variablen.
Und :
Die Frage, die man sich stellen sollte, ist also nicht, welche dieser beiden Operationen schneller ist, sondern welche dieser beiden Operationen genauer ausdrückt, was Sie erreichen möchten. Ich gehe davon
i++
aus++i
, dass es niemals einen Grund gibt, den Wert einer Variablen zu kopieren, die Variable zu erhöhen und die Kopie dann wegzuwerfen , wenn Sie den Wert des Ausdrucks nicht verwenden .
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.
i++
der gleichen Art und Weise würde ich Code i += n
oder i = i + n
, dh in Form Ziel Verb - Objekt , mit dem Zieloperanden nach links des Verbs Operator. Im Fall von i++
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 z int
. 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. Denn i++
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.
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
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.
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
versus i++
nur innerhalb des Ausdrucks, den er findet, Sinn macht.
Im Fall von for(i=0; i<n; i++)
ist das i++
allein in seinem eigenen Ausdruck: Es gibt einen Sequenzpunkt vor dem i++
und einen nach ihm. Somit ist der einzige generierte Maschinencode "Erhöhen i
um 1
" 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öhen i
um 1
" erhalten.
Die Unterschiede zwischen ++i
und i++
nur in Ausdrücken wie array[i++] = x;
versus array[++i] = x;
. Einige mögen argumentieren und sagen, dass der Postfix bei solchen Operationen langsamer sein wird, weil das Register, in dem sich das Postfix i
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.
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.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
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.
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:
for (i = 0; i < 100; i++)
In jeder Schleife haben Sie jeweils eine Anweisung für:
1
zu i
. i
kleiner als a ist 100
.i
kleiner als a ist 100
.Während eine Dekrementierungsschleife:
for (i = 100; i != 0; i--)
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.
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.
Zuallererst: Der Unterschied zwischen i++
und ++i
ist in C vernachlässigbar.
Zu den Details.
++i
ist schnellerIn C ++ ++i
ist es effizienter, wenn f i
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, bevor foo()
es aufgerufen wird, aber der alte Wert an übergeben werden muss foo()
. Folglich muss der Compiler eine Kopie i
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.
i++
kann schneller seinWenn kein Konstruktor / Destruktor aufgerufen werden muss, was in C immer der Fall ist ++i
und i++
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. Bei i++
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.
++i
oder i++
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 eine INC
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 von i++
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();
und if(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 ++.
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.
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:
a = ++b + c;
; increment b
LD A, [&b]
INC A
ST A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
Postfix-Inkrement:
a = b++ + c;
; load b
LD A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
; increment b
LD A, [&b]
INC A
ST A, [&b]
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 Sie b++
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 Ausdruck a = b++ + 1;
darum bittet (zum Beispiel, der geändert werden kann a = ++b;
).
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 als i--
). 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).
--i
und verwenden i++
.
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.
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?
myArray[i++] = "hello";
vs.
myArray[++i] = "hello";
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.
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, inkrementieren p
und q
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.