Ist x + = a schneller als x = x + a?

84

Ich habe Stroustrups "The C ++ Programming Language" gelesen, in der er sagt, dass es zwei Möglichkeiten gibt, einer Variablen etwas hinzuzufügen

x = x + a;

und

x += a;

Er bevorzugt, +=weil es höchstwahrscheinlich besser umgesetzt wird. Ich denke, er meint, dass es auch schneller funktioniert.
Aber ist es wirklich so? Wie überprüfe ich, ob es vom Compiler und anderen Dingen abhängt?

Chiffa
quelle
45
"The C ++ Programming Language" wurde erstmals 1985 veröffentlicht. Die neueste Version wurde 1997 veröffentlicht, und eine Sonderausgabe der Version von 1997 wurde 2000 veröffentlicht. Infolgedessen sind einige Teile sehr veraltet.
JoeG
5
Die beiden Zeilen könnten möglicherweise etwas völlig anderes bewirken. Sie müssen genauer sein.
Kerrek SB
26
Moderne Compiler sind klug genug, um diese Fragen als "veraltet" zu betrachten.
GD1
2
Öffnen Sie dies erneut, da in der doppelten Frage nach C und nicht nach C ++ gefragt wird.
Kev

Antworten:

212

Jeder Compiler wert sein Salz wird erzeugen genau die gleiche Maschinensprachsequenz für beiden Konstrukte für jeden eingebauten Typen ( int, floatusw.), solange die Aussage wirklich so einfach , wie x = x + a; und Optimierung aktiviert ist . (Insbesondere GCCs -O0, der Standardmodus, führen Anti-Optimierungen durch , z. B. das Einfügen völlig unnötiger Speicher in den Speicher, um sicherzustellen, dass Debugger immer variable Werte finden können.)

Wenn die Aussage jedoch komplizierter ist, können sie unterschiedlich sein. Angenommen, es fhandelt sich um eine Funktion, die dann einen Zeiger zurückgibt

*f() += a;

ruft fnur einmal an, während

*f() = *f() + a;

ruft es zweimal auf. Wenn fNebenwirkungen auftreten, ist einer der beiden falsch (wahrscheinlich der letztere). Selbst wenn fkeine Nebenwirkungen auftreten, kann der Compiler den zweiten Aufruf möglicherweise nicht eliminieren, sodass letzterer möglicherweise langsamer ist.

Und da es sich hier um C ++ handelt, ist die Situation für Klassentypen, die operator+und überladen, völlig anders operator+=. Wenn xes sich um einen solchen Typ handelt, wird vor der Optimierung in x += aübersetzt

x.operator+=(a);

während x = x + aübersetzt zu

auto TEMP(x.operator+(a));
x.operator=(TEMP);

Wenn die Klasse ordnungsgemäß geschrieben ist und der Optimierer des Compilers gut genug ist, werden beide dieselbe Maschinensprache generieren, aber es ist nicht sicher, wie es für integrierte Typen ist. Dies ist wahrscheinlich das, woran Stroustrup denkt, wenn er zur Verwendung ermutigt +=.

zwol
quelle
14
Es gibt noch einen weiteren Aspekt - die Lesbarkeit . Die C ++ - Sprache zum Hinzufügen exprzu varis var+=exprund zum Schreiben in die andere Richtung verwirrt die Leser.
Tadeusz Kopec
21
Wenn Sie feststellen, dass Sie schreiben *f() = *f() + a;, möchten Sie vielleicht einen genauen Blick darauf werfen, was Sie wirklich erreichen wollen ...
Adam Davis
3
Und wenn var = var + expr Sie verwirrt, var + = expr jedoch nicht, sind Sie der seltsamste Softwareentwickler, den ich je getroffen habe. Beide sind lesbar;
Stellen
7
@PiotrDobrogost: Was ist falsch daran, Fragen zu beantworten? In jedem Fall sollte es der Fragesteller sein, der nach Duplikaten gesucht hat.
Gorpik
4
@PiotrDobrogost scheint mir, als wärst du ein bisschen ... eifersüchtig ... Wenn du nach Duplikaten suchen willst, dann mach es. Zum einen bevorzuge ich es, die Fragen zu beantworten, anstatt nach Dupes zu suchen (es sei denn, es ist eine Frage, an die ich mich ausdrücklich erinnere, sie zuvor gesehen zu haben). Es kann manchmal schneller sein und ergo dem helfen, der die Frage schneller gestellt hat. Beachten Sie auch, dass dies nicht einmal eine Schleife ist. 1ist eine Konstante, akann ein flüchtiger, ein benutzerdefinierter Typ oder was auch immer sein. Völlig anders. Tatsächlich verstehe ich nicht, wie dies überhaupt geschlossen wurde.
Luchian Grigore
56

Sie können dies überprüfen, indem Sie sich die Demontage ansehen, die gleich sein wird.

Bei Basistypen sind beide gleich schnell.

Dies wird durch einen Debug-Build generiert (dh keine Optimierungen):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

Für benutzerdefinierte Typen , wo Sie überlasten operator +und operator +=hängt es von ihren jeweiligen Implementierungen.

Luchian Grigore
quelle
1
Nicht in allen Fällen wahr. Ich habe festgestellt, dass es schneller sein kann, eine Speicheradresse in ein Register zu laden, zu erhöhen und zurückzuschreiben, als den Speicherort direkt zu erhöhen (ohne Atomics). Ich werde versuchen, den Code zu rascheln ...
James
Wie wäre es mit benutzerdefinierten Typen? Gute Compiler sollten eine gleichwertige Assembly generieren, es gibt jedoch keine solche Garantie.
Mfontanini
1
@LuchianGrigore Nein, wenn a1 und xist volatileder Compiler generieren könnte inc DWORD PTR [x]. Das ist langsam.
James
1
@Chiffa nicht compilerabhängig, aber entwicklerabhängig. Sie könnten implementieren operator +, um nichts zu tun und operator +=die 100000. Primzahl zu berechnen und dann zurückzukehren. Das wäre natürlich eine dumme Sache, aber es ist möglich.
Luchian Grigore
3
@ James: Wenn Ihr Programm für den Leistungsunterschied zwischen ++xund sinnvoll ist, temp = x + 1; x = temp;sollte es höchstwahrscheinlich in Assembly anstatt in C ++ geschrieben werden ...
EmirCalabuch
11

Ja! Es ist schneller zu schreiben, schneller zu lesen und schneller herauszufinden, für letztere in dem Fall, der xNebenwirkungen haben könnte. Für die Menschen ist es also insgesamt schneller. Die menschliche Zeit kostet im Allgemeinen viel mehr als die Computerzeit, also müssen Sie danach gefragt haben. Richtig?

Mark Adler
quelle
8

Es hängt wirklich von der Art von x und a und der Implementierung von + ab. Zum

   T x, a;
   ....
   x = x + a;

Der Compiler muss ein temporäres T erstellen, um den Wert von x + a zu enthalten, während er ihn auswertet, das er dann x zuweisen kann. (Während dieses Vorgangs kann x oder a nicht als Arbeitsbereich verwendet werden.)

Für x + = a benötigt es keine temporäre.

Bei trivialen Typen gibt es keinen Unterschied.

Tom Tanner
quelle
8

Der Unterschied zwischen x = x + aund x += aist der Arbeitsaufwand, den die Maschine durchmachen muss - einige Compiler optimieren ihn möglicherweise (und tun dies normalerweise), aber wenn wir die Optimierung für eine Weile ignorieren, geschieht dies normalerweise im vorherigen Code-Snippet, dem Die Maschine muss den Wert xzweimal nachschlagen , während in letzterem Fall diese Suche nur einmal erfolgen muss.

Wie bereits erwähnt, sind die meisten Compiler heute jedoch intelligent genug, um die Anweisungen zu analysieren und die daraus resultierenden erforderlichen Maschinenanweisungen zu reduzieren.

PS: Erste Antwort zum Stapelüberlauf!

Sagar Ahire
quelle
6

Da Sie dieses C ++ beschriftet haben, können Sie anhand der beiden von Ihnen veröffentlichten Anweisungen nichts erkennen. Sie müssen wissen, was 'x' ist (es ist ein bisschen wie die Antwort '42'). Wenn xes sich um einen POD handelt, wird es keinen großen Unterschied machen. Wenn xes sich jedoch um eine Klasse handelt, kann es zu Überladungen für die Methoden operator +und kommen operator +=, die unterschiedliche Verhaltensweisen aufweisen können, die zu sehr unterschiedlichen Ausführungszeiten führen.

Skizz
quelle
6

Wenn Sie sagen, +=Sie machen dem Compiler das Leben viel einfacher. Damit der Compiler erkennt, dass dies x = x+adasselbe ist wie x += a, muss der Compiler

  • Analysieren Sie die linke Seite ( x), um sicherzustellen, dass sie keine Nebenwirkungen hat und sich immer auf denselben l-Wert bezieht. Zum Beispiel könnte es sein z[i], und es muss sicherstellen, dass beide zund inicht ändern.

  • Analysieren Sie die rechte Seite ( x+a) und stellen Sie sicher, dass es sich um eine Summierung handelt und dass die linke Seite einmal und nur einmal auf der rechten Seite vorkommt, obwohl sie wie in transformiert werden könnte z[i] = a + *(z+2*0+i).

Wenn das, was meinen Sie ist hinzuzufügen , azu xdem Compiler Schriftsteller schätzt es , wenn Sie nur sagen , was Sie meinen. Auf diese Weise sind Sie nicht der Teil des Compilers ausübt , dass seine Verfasser hofft er / sie bekam alle Fehler aus, und das nicht tatsächlich Leben einfacher für Sie, wenn Sie ehrlich nicht den Kopf bekommen kann aus des Fortran-Modus.

Mike Dunlavey
quelle
5

Stellen Sie sich als konkretes Beispiel einen einfachen komplexen Zahlentyp vor:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

Für den Fall a = a + b muss eine zusätzliche temporäre Variable erstellt und anschließend kopiert werden.

Random832
quelle
Dies ist ein sehr schönes Beispiel, das zeigt, wie 2 Operatoren implementiert wurden.
Grijesh Chauhan
5

Sie stellen die falsche Frage.

Es ist unwahrscheinlich, dass dies die Leistung einer App oder Funktion beeinträchtigt. Selbst wenn dies der Fall wäre, müssen Sie den Code profilieren und wissen, wie er Sie mit Sicherheit beeinflusst. Anstatt sich auf dieser Ebene Sorgen zu machen, welche schneller ist, ist es viel wichtiger , in Bezug auf Klarheit, Korrektheit und Lesbarkeit zu denken.

Dies gilt insbesondere dann, wenn Sie bedenken, dass sich Compiler im Laufe der Zeit weiterentwickeln, auch wenn dies ein wesentlicher Leistungsfaktor ist. Jemand könnte eine neue Optimierung finden und die richtige Antwort heute kann morgen falsch werden. Es ist ein klassischer Fall vorzeitiger Optimierung.

Das soll nicht heißen, dass Leistung überhaupt keine Rolle spielt ... Nur dass es der falsche Ansatz ist, um Ihre Perfektionsziele zu erreichen. Der richtige Ansatz besteht darin, mithilfe von Profiling-Tools zu ermitteln, wo Ihr Code tatsächlich seine Zeit verbringt und wo Sie sich auf Ihre Bemühungen konzentrieren können.

Joel Coehoorn
quelle
Zugegeben, aber es war als einfache Frage gedacht, nicht als großes Bild "Wann sollte ich einen solchen Unterschied in Betracht ziehen".
Chiffa
1
Die Frage des OP war absolut legitim, wie die Antworten (und Gegenstimmen) anderer zeigen. Obwohl wir Ihren Punkt (erstes Profil, etc.) ist es auf jeden Fall ist interessant , diese Art von Sachen zu wissen - sind Sie wirklich jede und jede triviale Aussage , die Sie zum Profil schreiben, Profil , die Ergebnisse jeder Entscheidung , die Sie nehmen? Selbst wenn es Leute auf SO gibt, die den Fall bereits studiert, profiliert, zerlegt und eine allgemeine Antwort zu geben haben?
4

Ich denke, das sollte von der Maschine und ihrer Architektur abhängen. Wenn seine Architektur eine indirekte Speicheradressierung zulässt, KANN der Compiler-Writer stattdessen einfach diesen Code verwenden (zur Optimierung):

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

Wobei übersetzt werden i = i + ykönnte (ohne Optimierung):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


iAllerdings sollten auch andere Komplikationen in Betracht gezogen werden, z. B. ob es sich um eine Funktion handelt, die einen Zeiger zurückgibt usw. Die meisten Compiler auf Produktionsebene, einschließlich GCC, erzeugen für beide Anweisungen denselben Code (wenn sie Ganzzahlen sind).

Aniket Inge
quelle
2

Nein, beide Wege werden gleich behandelt.

CloudyMarble
quelle
10
Nicht, wenn es sich um einen benutzerdefinierten Typ mit überladenen Operatoren handelt.