Gibt es einen Leistungsunterschied zwischen i ++ und ++ i in C?

Antworten:

396

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 ++ials 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 ++iund 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
Mark Harrison
quelle
9
Ich weiß, dass es sich bei dieser Frage um C handelt, aber ich würde gerne wissen, ob Browser diese Optimierung für Javascript durchführen können.
TM.
69
Das "Nein" gilt also für den einen Compiler, mit dem Sie getestet haben.
Andreas
173
@Andreas: Gute Frage. Ich war früher ein Compiler-Writer und hatte die Möglichkeit, diesen Code auf vielen CPUs, Betriebssystemen und Compilern zu testen. Der einzige Compiler, den ich gefunden habe, der den i ++ - Fall nicht optimiert hat (tatsächlich der Compiler, der mich professionell darauf aufmerksam gemacht hat), war der Software Toolworks C80-Compiler von Walt Bilofsky. Dieser Compiler war für Intel 8080 CP / M-Systeme. Man kann mit Sicherheit sagen, dass jeder Compiler, der diese Optimierung nicht enthält, nicht für den allgemeinen Gebrauch gedacht ist.
Mark Harrison
22
Auch wenn der Leistungsunterschied vernachlässigbar ist und in vielen Fällen optimiert wird, beachten Sie bitte, dass die Verwendung ++ianstelle 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 ++iwie zu tippen i++, gibt es wirklich keine Entschuldigung, es überhaupt nicht zu verwenden ++i.
Monokrom
7
@monokrome Da Entwickler ihre eigene Implementierung für die Präfix- und Postfix-Operatoren in vielen Sprachen bereitstellen können, ist dies möglicherweise nicht immer eine akzeptable Lösung für einen Compiler, ohne zuvor die Funktionen zu vergleichen, die möglicherweise nicht trivial sind.
Pickypg
112

Aus Effizienz versus Absicht von Andrew Koenig:

Erstens ist es alles ++iandere als offensichtlich, dass dies effizienter ist als i++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.

Sébastien RoccaSerra
quelle
5
Vergessen wir nicht, dass auch andere unäre Operatoren Präfixe sind. Ich denke, ++ i ist die "semantische" Art, einen unären Operator zu verwenden, während i ++ vorhanden ist, um einem bestimmten Bedarf zu entsprechen (Auswertung vor dem Hinzufügen).
TM.
9
Wenn der resultierende Wert nicht verwendet wird, gibt es keinen Unterschied in der Semantik: Das heißt, es gibt keine Grundlage, um das eine oder andere Konstrukt zu bevorzugen.
Eamon Nerbonne
3
Als Leser sehe ich einen Unterschied. Als Schriftsteller werde ich meine Absicht zeigen, indem ich eine über die andere wähle. Es ist nur eine Angewohnheit, mit meinen Programmiererfreunden und Teamkollegen über Code zu kommunizieren :)
Sébastien RoccaSerra
11
Koenigs Beratung, I - Code Nach i++der gleichen Art und Weise würde ich Code i += noder 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 .
David R Tribble
4
Wenn Sie versuchen, i zu erhöhen, drücken i ++ und ++ i Ihre Absicht korrekt aus. Es gibt keinen Grund, einen dem anderen vorzuziehen. Als Leser sehe ich keinen Unterschied. Werden Ihre Kollegen verwirrt, wenn sie i ++ sehen und denken, vielleicht ist dies ein Tippfehler und er wollte i nicht erhöhen?
Dan Carter
46

Eine bessere Antwort ist, dass dies ++imanchmal schneller, aber niemals langsamer ist.

Jeder scheint davon auszugehen, dass ies sich um einen regulären eingebauten Typ handelt, wie z int. In diesem Fall gibt es keinen messbaren Unterschied.

Wenn ies 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 ++itSie 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 ++iSie 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, ++ianstatt dort, i++wo das Ergebnis nicht beeinflusst wird.

Andrew Grant
quelle
36
Die Frage lautet explizit C, kein Verweis auf C ++.
Dan Cristoloveanu
5
Diese (zugegebenermaßen alte) Frage betraf C, nicht C ++, aber ich denke, es ist auch erwähnenswert, dass in C ++, selbst wenn eine Klasse Post- und Pre-Fix-Operatoren implementiert, diese nicht unbedingt miteinander verbunden sind. Zum Beispiel kann bar ++ ein Datenelement inkrementieren, während ++ bar ein anderes Datenelement inkrementieren kann, und in diesem Fall hätten Sie auch keine Option, da die Semantik unterschiedlich ist.
Kevin
-1 Dies ist zwar ein guter Rat für C ++ - Programmierer, beantwortet jedoch nicht die Frage, die mit C gekennzeichnet ist, im geringsten. In C spielt es absolut keine Rolle, ob Sie Präfix oder Postfix verwenden.
Lundin
@Pacerier Die Frage ist mit C und nur mit C gekennzeichnet. Warum nehmen Sie an, dass sie daran nicht interessiert sind? Wäre es angesichts der Funktionsweise von SO nicht klug anzunehmen, dass sie nur an C interessiert sind und nicht an Java, C #, COBOL oder einer anderen Sprache außerhalb des Themas?
Lundin
18

Kurze Antwort:

Es gibt nie einen Unterschied zwischen i++und ++iin 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 ++iversus 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 ium 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 ium 1" erhalten.

Die Unterschiede zwischen ++iund 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 ibefindet, 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:

  • Speichern Sie den Wert von iin Register A.
  • Speichern Sie die Adresse des Arrays in Register B.
  • A und B hinzufügen, Ergebnisse in A speichern.
  • Speichern Sie an dieser neuen Adresse, die durch A dargestellt wird, den Wert von x.
  • Speichern Sie den Wert von iin Register A // ineffizient, da zusätzliche Anweisungen hier bereits einmal ausgeführt wurden.
  • Inkrementregister A.
  • Speichern Sie Register A in i.

Der Compiler kann den Code genauso gut effizienter erstellen, z.

  • Speichern Sie den Wert von iin Register A.
  • Speichern Sie die Adresse des Arrays in Register B.
  • Addiere A und B, speichere die Ergebnisse in B.
  • Inkrementregister A.
  • Speichern Sie Register A in i.
  • ... // Rest des Codes.

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.

Lundin
quelle
3
Niemand sagte "Präfix ++ ist immer schneller". Das ist falsch zitiert. Was sie sagten war "Postfix ++ ist nie schneller".
Pacerier
2
@ Pacerier Ich zitiere keine bestimmte Person, sondern nur eine weit verbreitete, falsche Überzeugung.
Lundin
@rbaleksandar C ++! = C.
Lundin
@rbaleksandar Die Frage und die Antwort sprechen beide über C ++. Das ist anders als C, da es Operatorüberladung und Kopierkonstruktoren usw. hat
Lundin
3
@rbaleksandar c ++ == c && ++ c! = c
sadljkfhalskdjfh
17

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.

JProgrammer
quelle
4
Ich denke, seine Frage richtete sich an C, aber für C ++ haben Sie absolut Recht, und außerdem sollten C-Leute dies übernehmen, da sie es auch für C ++ verwenden können. Ich sehe viel zu oft, dass C-Programmierer die Postfix-Syntax verwenden ;-)
Anders Rune Jensen
-1 Dies ist zwar ein guter Rat für C ++ - Programmierer, beantwortet jedoch nicht die Frage, die mit C gekennzeichnet ist, im geringsten. In C spielt es absolut keine Rolle, ob Sie Präfix oder Postfix verwenden.
Lundin
1
@Lundin Präfix und Postifx spielen in C eine Rolle, wie von Andreas beantwortet . Sie können nicht davon ausgehen, dass der Compiler wegoptimiert
JProgrammer
@JProgrammer Sie spielen keine Rolle gemäß der Antwort von Ihnen aufrichtig :) Wenn Sie feststellen, dass sie unterschiedlichen Code liefern, liegt dies entweder daran, dass Sie keine Optimierungen aktiviert haben, oder daran, dass der Compiler schlecht ist. Wie auch immer, Ihre Antwort ist nicht zum Thema, da die Frage über C.
Lundin
16

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. Hinzufügen 1zu i.
  2. Vergleichen Sie, ob ikleiner als a ist 100.
  3. Ein bedingter Zweig, wenn er ikleiner als a ist 100.

Während eine Dekrementierungsschleife:

for (i = 100; i != 0; i--)

Die Schleife enthält eine Anweisung für:

  1. Dekrementieren i, Setzen des CPU-Registerstatusflags.
  2. Eine bedingte Verzweigung abhängig vom CPU-Registerstatus ( Z==0).

Dies funktioniert natürlich nur beim Dekrementieren auf Null!

Erinnert an das ARM System Developer's Guide.

Tonylo
quelle
Gut. Aber führt dies nicht zu weniger Cache-Treffern?
AndreasT
Es gibt einen alten seltsamen Trick aus Codeoptimierungsbüchern, der den Vorteil eines Null-Test-Zweigs mit einer inkrementierten Adresse kombiniert. Hier ist ein Beispiel in Hochsprache (wahrscheinlich nutzlos, da viele Compiler intelligent genug sind, um es durch weniger effizienten, aber häufigeren Schleifencode zu ersetzen ): int a [N]; für (i = -N; i; ++ i) a [N + i] + = 123;
Noop
@noop Könnten Sie möglicherweise erläutern, auf welche Codeoptimierungsbücher Sie verweisen? Ich habe mich bemüht, gute zu finden.
Mezamorphic
7
Diese Antwort ist überhaupt keine Antwort auf die Frage.
Monokrom
@mezamorphic Für x86 sind meine Quellen: Intel Optimierungshandbücher, Agner Fog, Paul Hsieh, Michael Abrash.
Noop
11

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.

Andy Lester
quelle
3
Ich halte es für falsch, vage Verbesserungen der Lesbarkeit den tatsächlichen Effizienzgewinnen und der allgemeinen Klarheit der Absichten vorzuziehen.
Noop
4
Mein Begriff "Programmierer-Lesezeit" ist ungefähr analog zu "Klarheit der Absicht". "Tatsächliche Effizienzgewinne" sind oft unermesslich und nahe genug bei Null, um sie als Null zu bezeichnen. Im Fall des OP ist die Frage, welcher schneller ist, Zeitverschwendung und Gedankeneinheiten des Programmierers, es sei denn, der Code wurde profiliert, um festzustellen, dass ++ i ein Engpass ist.
Andy Lester
2
Der Unterschied in der Lesbarkeit zwischen ++ i und i ++ ist nur eine Frage der persönlichen Präferenz, aber ++ i impliziert eindeutig eine einfachere Bedienung als i ++, obwohl das Ergebnis für triviale Fälle und einfache Datentypen bei der Optimierung des Compilers gleichwertig ist. Daher ist ++ i für mich ein Gewinner, wenn bestimmte Eigenschaften des Post-Inkrements nicht erforderlich sind.
Noop
Du sagst was ich sage. Es ist wichtiger, Absichten zu zeigen und die Lesbarkeit zu verbessern, als sich um "Effizienz" zu sorgen.
Andy Lester
2
Ich kann immer noch nicht zustimmen. Wenn die Lesbarkeit auf Ihrer Prioritätenliste höher steht, ist Ihre Programmiersprache möglicherweise falsch. Der Hauptzweck von C / C ++ ist das Schreiben von effizientem Code.
Noop
11

Zuallererst: Der Unterschied zwischen i++und ++iist in C vernachlässigbar.


Zu den Details.

1. Das bekannte C ++ - Problem: ++iist schneller

In C ++ ++iist es effizienter, wenn f ieine Art Objekt mit einem überladenen Inkrementoperator ist.

Warum?
In ++iwird 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 idavon 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 sein

Wenn kein Konstruktor / Destruktor aufgerufen werden muss, was in C immer der Fall ist ++iund 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 ++imuss 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.

cmaster - Monica wieder einsetzen
quelle
Zu Punkt 2: Wenn das ++ioder 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 INCAssembly-Anweisung.
Shahbaz
1
@ Shahbaz Das ist vollkommen richtig, aber nicht der Punkt. 1) Auch wenn die Semantik ist unterschiedlich, beide i++und ++ikann 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 ++idie CPU den anderen Befehl nach dem Inkrement planen muss .
cmaster
4
@ Shahbaz Als Beispiel: 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.
cmaster
1
Guter Punkt. Konstanten tauchen häufig auf (wie <zum Beispiel vs <=), wo sie ++normalerweise verwendet werden, so dass die Konvertierung zwischen den obwohl oft leicht möglich ist.
Shahbaz
1
Ich mag Punkt 2, aber das gilt nur, wenn der Wert verwendet wird, richtig? Die Frage lautet "Wenn der resultierende Wert nicht verwendet wird?", Kann also verwirrend sein.
Jinawee
7

@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 ++.

Andreas
quelle
Nur neugierig ... warum verwenden Sie 4 verschiedene C-Compiler in einem Projekt? Oder in einem Team oder einer Firma?
Lawrence Dol
3
Beim Erstellen von Spielen für Konsolen bringt jede Plattform ihren eigenen Compiler / Toolchain mit. In einer perfekten Welt könnten wir gcc / clang / llvm für alle Ziele verwenden, aber in dieser Welt müssen wir uns mit Microsoft, Intel, Metroworks, Sony usw. abfinden
Andreas
5

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.

Kristopher Johnson
quelle
4

Ich kann mir eine Situation vorstellen, in der Postfix langsamer ist als Präfixinkrement:

Stellen Sie sich vor, ein Prozessor mit Register Awird 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 bneu 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 ++bohne 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 ++bfalsch , 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;).

Shahbaz
quelle
4

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).

daShier
quelle
2
Sehr esoterisch, aber sehr interessant!
Mark Harrison
@daShier. +1, obwohl ich nicht einverstanden bin, ist dies nicht so esoterisch oder sollte es zumindest nicht sein. C wurde in den frühen 70er Jahren gemeinsam mit Unix bei AT & T Bell Labs entwickelt, als der PDP-11 der Zielprozessor war. Im Unix-Quellcode aus dieser Zeit ist das Post-Inkrement "i ++" häufiger anzutreffen, teilweise weil die Entwickler wussten, wann der Wert "j = i ++" zugewiesen oder als Index "a [i ++] = n" verwendet wurde. Der Code wäre etwas schneller (und kleiner). Es scheint, dass sie sich angewöhnt haben, Post-Inkremente zu verwenden, es sei denn, dies war erforderlich. Andere lernten durch Lesen ihres Codes und nahmen auch diese Angewohnheit auf.
Jimhark
Der 68000 verfügt über die gleiche Funktion. Nachinkrementierung und Vordekrementierung werden in der Hardware unterstützt (als Adressierungsmodi), jedoch nicht umgekehrt. Das Motorola-Team war vom DEC PDP-11 inspiriert.
Jimhark
2
@ Jimhark, ja, ich bin einer dieser PDP-11-Programmierer, die auf den 68000 umgestiegen sind und immer noch --iund verwenden i++.
DaShier
2

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
Ihr Beitrag ist C ++ - spezifisch, während sich die Frage auf C bezieht. In jedem Fall ist Ihre Antwort falsch: Bei benutzerdefinierten Post-Inkrement-Operatoren kann der Compiler im Allgemeinen keinen so effizienten Code erstellen.
Konrad Rudolph
Er sagt "wenn die Funktion inline wird", und das macht seine Argumentation richtig.
Blaisorblade
Da C keine Operatorüberladung bietet, ist die Frage vor und nach dem Eingriff weitgehend uninteressant. Der Compiler kann eine nicht verwendete Temperatur mithilfe derselben Logik optimieren, die auf jeden anderen nicht verwendeten, primitiv berechneten Wert angewendet wird. Ein Beispiel finden Sie in der ausgewählten Antwort.
0

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.

Jason Z.
quelle
1
@Jason Z Die Compileroptimierung erfolgt vor Abschluss der Generierung der Assembly. Es wird festgestellt, dass die Variable i nirgendwo anders in derselben Zeile verwendet wird. Wenn Sie also ihren Wert beibehalten, ist dies eine Verschwendung. Wahrscheinlich wird sie effektiv in i ++ umgedreht. Aber das ist nur eine Vermutung. Ich kann es kaum erwarten, bis einer meiner Dozenten versucht zu sagen, dass es schneller ist und ich der Typ bin, der eine Theorie mit praktischen Beweisen korrigiert. Ich kann die Feindseligkeit schon fast spüren ^ _ ^
Tarks
3
"Mein C ist etwas rostig, also entschuldige ich mich im Voraus." An Ihrem C ist nichts auszusetzen, aber Sie haben die ursprüngliche Frage nicht vollständig gelesen: "Gibt es einen Leistungsunterschied zwischen i ++ und ++ i, wenn der resultierende Wert nicht verwendet wird? " Beachten Sie die Kursivschrift. In Ihrem Beispiel des ‚Ergebnis‘ von i ++ / ++ i wird verwendet, aber in den idiomatischen for - Schleife, das ‚Ergebnis‘ des Pre / Post - Inkrement Operators wird nicht verwendet , so dass der Compiler tun kann , was er will.
Roddy
2
In Ihrem Beispiel wäre der Code anders, da Sie den Wert verwenden. In dem Beispiel, in dem sie identisch waren, wurde nur das ++ für das Inkrement verwendet und nicht der von beiden zurückgegebene Wert.
John Gardner
1
Fix: "Der Compiler würde sehen, dass der Rückgabewert von i ++ nicht verwendet wird, also würde er ihn auf ++ i umdrehen." Was Sie geschrieben haben, ist auch deshalb falsch, weil Sie i nicht zusammen mit einem von i ++, i ++ in derselben Zeile (Anweisung) haben können. Dieses Ergebnis ist undefiniert.
Blaisorblade
1
Das Ändern 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 pund qeinmal 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.
Supercat