Wir haben die Frage, ob es einen Leistungsunterschied zwischen i++
und ++i
in C gibt .
Was ist die Antwort für C ++?
c++
performance
oop
post-increment
pre-increment
Mark Harrison
quelle
quelle
Antworten:
[Zusammenfassung: Verwenden
++i
Sie diese Option, wenn Sie keinen bestimmten Grund für die Verwendung habeni++
.]Für C ++ ist die Antwort etwas komplizierter.
Wenn
i
es sich um einen einfachen Typ handelt (keine Instanz einer C ++ - Klasse), gilt die Antwort für C ("Nein, es gibt keinen Leistungsunterschied") , da der Compiler den Code generiert.Wenn jedoch
i
ist eine Instanz einer C ++ Klasse, danni++
und++i
werden Anrufe zu einer deroperator++
Funktionen. Hier ist ein Standardpaar dieser Funktionen:Da der Compiler keinen Code generiert, sondern nur eine
operator++
Funktion aufruft , gibt es keine Möglichkeit, dietmp
Variable und den zugehörigen Kopierkonstruktor zu optimieren . Wenn der Kopierkonstruktor teuer ist, kann dies erhebliche Auswirkungen auf die Leistung haben.quelle
Ja. Es gibt.
Der ++ - Operator kann als Funktion definiert sein oder nicht. Für primitive Typen (int, double, ...) sind die Operatoren integriert, sodass der Compiler Ihren Code wahrscheinlich optimieren kann. Bei einem Objekt, das den ++ - Operator definiert, sind die Dinge jedoch anders.
Die Funktion operator ++ (int) muss eine Kopie erstellen. Dies liegt daran, dass von postfix ++ erwartet wird, dass es einen anderen Wert als den darin enthaltenen zurückgibt: Es muss seinen Wert in einer temporären Variablen enthalten, seinen Wert erhöhen und den temporären Wert zurückgeben. Im Fall von Operator ++ (), Präfix ++, muss keine Kopie erstellt werden: Das Objekt kann sich selbst inkrementieren und sich dann einfach selbst zurückgeben.
Hier ist eine Illustration des Punktes:
Jedes Mal, wenn Sie operator ++ (int) aufrufen, müssen Sie eine Kopie erstellen, und der Compiler kann nichts dagegen tun. Wenn Sie die Wahl haben, verwenden Sie den Operator ++ (). Auf diese Weise speichern Sie keine Kopie. Dies kann bei vielen Inkrementen (große Schleife?) Und / oder großen Objekten von Bedeutung sein.
quelle
C t(*this); ++(*this); return t;
In der zweiten Zeile erhöhen Sie den Zeiger nach rechts. Wie wird alsot
aktualisiert, wenn Sie dies erhöhen? Wurden die Werte nicht bereits kopiertt
?The operator++(int) function must create a copy.
Nein ist es nicht. Nicht mehr Exemplare alsoperator++()
Hier ist ein Benchmark für den Fall, dass sich Inkrementoperatoren in verschiedenen Übersetzungseinheiten befinden. Compiler mit g ++ 4.5.
Ignorieren Sie vorerst die Stilprobleme
O (n) Inkrement
Prüfung
Ergebnisse
Ergebnisse (Zeitangaben in Sekunden) mit g ++ 4.5 auf einer virtuellen Maschine:
O (1) Inkrement
Prüfung
Nehmen wir nun die folgende Datei:
Es macht nichts in der Inkrementierung. Dies simuliert den Fall, dass die Inkrementierung eine konstante Komplexität aufweist.
Ergebnisse
Die Ergebnisse variieren jetzt extrem:
Fazit
Leistungsmäßig
Wenn Sie den vorherigen Wert nicht benötigen, machen Sie es sich zur Gewohnheit, das Vorinkrement zu verwenden. Seien Sie auch mit eingebauten Typen konsistent, Sie werden sich daran gewöhnen und laufen nicht Gefahr, unnötige Leistungseinbußen zu erleiden, wenn Sie jemals einen eingebauten Typ durch einen benutzerdefinierten Typ ersetzen.
Semantisch
i++
sagtincrement i, I am interested in the previous value, though
.++i
sagtincrement i, I am interested in the current value
oderincrement i, no interest in the previous value
. Auch hier werden Sie sich daran gewöhnen, auch wenn Sie es gerade nicht sind.Knuth.
Vorzeitige Optimierung ist die Wurzel allen Übels. Da ist vorzeitige Pessimisierung.
quelle
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
Tiefenüberquerung, sodass Sie sich nicht um die tatsächliche Baumstruktur (BSP, kd, Quadtree, Octree Grid usw.) kümmern müssen. Eine solche würde Iterator braucht etwas Zustand zu halten, zum Beispielparent node
,child node
,index
und solche Sachen. Alles in allem ist meine Haltung, auch wenn es nur wenige Beispiele gibt, ...Es ist nicht ganz richtig zu sagen, dass der Compiler die temporäre Variablenkopie im Postfix-Fall nicht optimieren kann. Ein schneller Test mit VC zeigt, dass dies zumindest in bestimmten Fällen möglich ist.
Im folgenden Beispiel ist der generierte Code beispielsweise für Präfix und Postfix identisch:
Unabhängig davon, ob Sie ++ testFoo oder testFoo ++ ausführen, erhalten Sie immer noch den gleichen resultierenden Code. In der Tat, ohne die Zählung vom Benutzer einzulesen, brachte der Optimierer das Ganze auf eine Konstante. Also das:
Daraus resultierte Folgendes:
Während es sicherlich der Fall ist, dass die Postfix-Version langsamer sein könnte, kann es durchaus sein, dass das Optimierungsprogramm gut genug ist, um die temporäre Kopie zu entfernen, wenn Sie sie nicht verwenden.
quelle
Im Google C ++ Style Guide heißt es:
quelle
Ich möchte auf einen ausgezeichneten Beitrag von Andrew Koenig zu Code Talk in letzter Zeit hinweisen.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
In unserem Unternehmen verwenden wir auch die Konvention von ++ iter, um gegebenenfalls Konsistenz und Leistung zu gewährleisten. Aber Andrew wirft übersehene Details in Bezug auf Absicht und Leistung auf. Es gibt Zeiten, in denen wir iter ++ anstelle von ++ iter verwenden möchten.
Entscheiden Sie also zuerst über Ihre Absicht, und wenn Pre oder Post keine Rolle spielen, entscheiden Sie sich für Pre, da dies einen gewissen Leistungsvorteil bietet, indem Sie vermeiden, dass ein zusätzliches Objekt erstellt und geworfen wird.
quelle
@ Ketan
Offensichtlich haben Post und Pre-Inkrement unterschiedliche Semantiken, und ich bin sicher, alle sind sich einig, dass Sie bei Verwendung des Ergebnisses den entsprechenden Operator verwenden sollten. Ich denke, die Frage ist, was man tun soll, wenn das Ergebnis verworfen wird (wie in
for
Schleifen). Die Antwort auf diese Frage (IMHO) lautet: Da die Leistungsaspekte bestenfalls vernachlässigbar sind, sollten Sie das tun, was natürlicher ist. Für mich++i
ist das natürlicher, aber meine Erfahrung zeigt mir, dass ich in einer Minderheit bin und die Verwendungi++
für die meisten Leute, die Ihren Code lesen, weniger Metallaufwand verursacht .Schließlich heißt die Sprache deshalb nicht "
++C
". [*][*] Fügen Sie eine obligatorische Diskussion über
++C
einen logischeren Namen ein.quelle
Wenn der Rückgabewert nicht verwendet wird, wird garantiert, dass der Compiler im Fall von ++ i kein temporäres Element verwendet . Nicht garantiert schneller, aber garantiert nicht langsamer.
Bei Verwendung des Rückgabewerts ermöglicht i ++ dem Prozessor, sowohl das Inkrement als auch die linke Seite in die Pipeline zu verschieben, da sie nicht voneinander abhängig sind. ++ Ich kann die Pipeline blockieren, da der Prozessor die linke Seite erst starten kann, wenn sich die Vorinkrementierungsoperation vollständig durchgeschlichen hat. Auch hier ist ein Pipeline-Stillstand nicht garantiert, da der Prozessor möglicherweise andere nützliche Dinge zum Einstecken findet.
quelle
Mark: Ich wollte nur darauf hinweisen, dass Operator ++ gute Kandidaten für Inline sind, und wenn der Compiler dies wählt, wird die redundante Kopie in den meisten Fällen eliminiert. (zB POD-Typen, die normalerweise Iteratoren sind.)
Trotzdem ist es in den meisten Fällen immer noch besser, ++ iter zu verwenden. :-)
quelle
Der Leistungsunterschied zwischen
++i
undi++
wird deutlicher, wenn Sie Operatoren als wertrückgebende Funktionen betrachten und wie sie implementiert werden. Um das Verständnis zu erleichtern, werden die folgenden Codebeispiele so verwendet,int
als wäre es einstruct
.++i
erhöht die Variable und gibt dann das Ergebnis zurück. Dies kann direkt und mit minimaler CPU-Zeit erfolgen, wobei in vielen Fällen nur eine Codezeile erforderlich ist:Das Gleiche kann man aber nicht sagen
i++
.Nach dem Inkrementieren
i++
wird häufig als Rückgabe des ursprünglichen Werts vor dem Inkrementieren angesehen. Eine Funktion kann jedoch nur dann ein Ergebnis zurückgeben, wenn es fertig ist . Infolgedessen ist es erforderlich, eine Kopie der Variablen mit dem ursprünglichen Wert zu erstellen, die Variable zu erhöhen und dann die Kopie mit dem ursprünglichen Wert zurückzugeben:Wenn es keinen funktionalen Unterschied zwischen Pre-Inkrement und Post-Inkrement gibt, kann der Compiler eine Optimierung durchführen, sodass zwischen beiden kein Leistungsunterschied besteht. Wenn jedoch ein zusammengesetzter Datentyp wie ein
struct
oderclass
beteiligt ist, wird der Kopierkonstruktor nach dem Inkrementieren aufgerufen, und es ist nicht möglich, diese Optimierung durchzuführen, wenn eine tiefe Kopie erforderlich ist. Daher ist das Vorinkrementieren im Allgemeinen schneller und erfordert weniger Speicher als das Nachinkrementieren.quelle
@Mark: Ich habe meine vorherige Antwort gelöscht, weil es ein bisschen umgedreht war, und habe allein dafür eine Ablehnung verdient. Ich denke tatsächlich, dass es eine gute Frage in dem Sinne ist, dass sie fragt, was viele Leute denken.
Die übliche Antwort ist, dass ++ i schneller ist als i ++, und zweifellos ist es das, aber die größere Frage lautet: "Wann sollte es dich interessieren?"
Wenn der Anteil der CPU-Zeit, die für das Inkrementieren von Iteratoren aufgewendet wird, weniger als 10% beträgt, ist dies möglicherweise egal.
Wenn der Anteil der CPU-Zeit, die für das Inkrementieren von Iteratoren aufgewendet wird, mehr als 10% beträgt, können Sie überprüfen, welche Anweisungen diese Iteration ausführen. Überprüfen Sie, ob Sie nur Ganzzahlen erhöhen können, anstatt Iteratoren zu verwenden. Die Chancen stehen gut, dass Sie dies könnten, und obwohl dies in gewissem Sinne weniger wünschenswert ist, stehen die Chancen gut, dass Sie im Wesentlichen die gesamte Zeit sparen, die Sie in diesen Iteratoren verbringen.
Ich habe ein Beispiel gesehen, bei dem das Iterator-Inkrementieren weit über 90% der Zeit in Anspruch nahm. In diesem Fall reduzierte das Inkrementieren von Ganzzahlen die Ausführungszeit im Wesentlichen um diesen Betrag. (dh besser als 10x Beschleunigung)
quelle
@ Wilhelmtell
Der Compiler kann das Temporäre entfernen. Wörtlich aus dem anderen Thread:
Der C ++ - Compiler kann stapelbasierte Provisorien entfernen, auch wenn dies das Programmverhalten ändert. MSDN-Link für VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
quelle
Ein Grund, warum Sie ++ i auch bei integrierten Typen verwenden sollten, bei denen es keinen Leistungsvorteil gibt, besteht darin, sich eine gute Angewohnheit anzueignen.
quelle
Beide sind so schnell;) Wenn Sie möchten, dass es die gleiche Berechnung für den Prozessor ist, ist es nur die Reihenfolge, in der es gemacht wird, die sich unterscheidet.
Zum Beispiel der folgende Code:
Stellen Sie die folgende Baugruppe her:
Sie sehen, dass es für a ++ und b ++ eine inkl. Mnemonik ist, also ist es die gleiche Operation;)
quelle
Bei der beabsichtigten Frage ging es darum, wann das Ergebnis nicht verwendet wird (das geht aus der Frage für C hervor). Kann jemand dies beheben, da die Frage "Community-Wiki" ist?
Über vorzeitige Optimierungen wird häufig Knuth zitiert. Das stimmt. aber Donald Knuth würde damit niemals den schrecklichen Code verteidigen, den man heutzutage sehen kann. Schon mal a = b + c unter Java Integers (nicht int) gesehen? Das sind 3 Boxing / Unboxing-Conversions. Solche Dinge zu vermeiden ist wichtig. Und nutzloses Schreiben von i ++ anstelle von ++ i ist der gleiche Fehler. EDIT: Wie Phresnel in einem Kommentar schön formuliert, kann dies als "vorzeitige Optimierung ist böse, ebenso wie vorzeitige Pessimisierung" zusammengefasst werden.
Sogar die Tatsache, dass Menschen eher an i ++ gewöhnt sind, ist ein unglückliches C-Erbe, das durch einen konzeptionellen Fehler von K & R verursacht wurde (wenn Sie dem Vorsatzargument folgen, ist das eine logische Schlussfolgerung; und die Verteidigung von K & R, weil sie K & R sind, ist bedeutungslos, sie sind bedeutungslos großartig, aber sie sind nicht großartig als Sprachdesigner; es gibt unzählige Fehler im C-Design, die von get () über strcpy () bis zur strncpy () -API reichen (sie sollte seit Tag 1 die strlcpy () -API haben). ).
Übrigens bin ich einer von denen, die nicht genug an C ++ gewöhnt sind, um ++ zu finden. Ich bin nervig zu lesen. Trotzdem benutze ich das, da ich anerkenne, dass es richtig ist.
quelle
++i
ärgerlicher alsi++
(tatsächlich fand ich es cooler), aber der Rest Ihres Beitrags erhält meine volle Bestätigung. Vielleicht einen Punkt hinzufügen "Vorzeitige Optimierung ist böse, ebenso wie vorzeitige Pessimisierung"strncpy
diente einem Zweck in den Dateisystemen, die sie zu der Zeit verwendeten; Der Dateiname war ein 8-stelliger Puffer und musste nicht nullterminiert werden. Sie können ihnen nicht die Schuld geben, dass sie 40 Jahre in der Zukunft der Sprachentwicklung nicht gesehen haben.strlcpy()
wurde durch die Tatsache gerechtfertigt, dass es noch nicht erfunden worden war.Zeit, den Leuten Juwelen der Weisheit zu vermitteln;) - Es gibt einen einfachen Trick, mit dem sich das C ++ - Postfix-Inkrement so ziemlich wie das Präfix-Inkrement verhält (Ich habe es für mich selbst erfunden, aber ich habe es auch im Code anderer Leute gesehen, also bin ich es nicht allein).
Grundsätzlich besteht der Trick darin, die Hilfsklasse zu verwenden, um das Inkrement nach der Rückkehr zu verschieben, und RAII kommt zur Rettung
Erfunden wurde für einige schwere benutzerdefinierte Iterator-Codes und verkürzt die Laufzeit. Die Kosten für Präfix und Postfix sind jetzt eine Referenz, und wenn dies ein benutzerdefinierter Operator ist, der sich stark bewegt, haben Präfix und Postfix für mich dieselbe Laufzeit ergeben.
quelle
++i
ist schneller alsi++
weil es keine alte Kopie des Wertes zurückgibt.Es ist auch intuitiver:
In diesem C-Beispiel wird "02" anstelle der erwarteten "12" gedruckt:
Gleiches gilt für C ++ :
quelle