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

352

Wir haben die Frage, ob es einen Leistungsunterschied zwischen i++und ++i in C gibt .

Was ist die Antwort für C ++?

Mark Harrison
quelle
Ich habe erneut getaggt, da diese beiden Tags der einfachste Weg sind, Fragen dieser Art zu finden. Ich ging auch andere durch, die keine zusammenhängenden Tags hatten, und gab ihnen zusammenhängende Tags.
George Stocker
104
Gibt es einen Leistungsunterschied zwischen C ++ und ++ C?
new123456
2
Artikel: Ist es sinnvoll, den Präfix-Inkrement-Operator ++ it anstelle des Postfix-Operators it ++ für Iteratoren zu verwenden? - viva64.com/de/b/0093

Antworten:

426

[Zusammenfassung: Verwenden ++iSie diese Option, wenn Sie keinen bestimmten Grund für die Verwendung haben i++.]

Für C ++ ist die Antwort etwas komplizierter.

Wenn ies 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 iist eine Instanz einer C ++ Klasse, dann i++und ++iwerden Anrufe zu einer der operator++Funktionen. Hier ist ein Standardpaar dieser Funktionen:

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

Da der Compiler keinen Code generiert, sondern nur eine operator++Funktion aufruft , gibt es keine Möglichkeit, die tmpVariable und den zugehörigen Kopierkonstruktor zu optimieren . Wenn der Kopierkonstruktor teuer ist, kann dies erhebliche Auswirkungen auf die Leistung haben.

Mark Harrison
quelle
3
Was der Compiler vermeiden kann, ist die zweite Kopie, die tmp zurückgibt, indem tmp im Aufrufer über NRVO zugewiesen wird, wie in einem anderen Kommentar erwähnt.
Blaisorblade
7
Kann der Compiler dies nicht vollständig vermeiden, wenn Operator ++ inline ist?
Eduard - Gabriel Munteanu
16
Ja, wenn Operator ++ inline ist und tmp nie verwendet wird, kann es entfernt werden, es sei denn, der Konstruktor oder Destruktor des tmp-Objekts hat Nebenwirkungen.
Zan Lynx
5
@kriss: Der Unterschied zwischen C und C ++ besteht darin, dass Sie in C die Garantie haben, dass der Operator inline ist, und an diesem Punkt kann ein anständiger Optimierer den Unterschied beseitigen. Stattdessen können Sie in C ++ nicht von Inlining ausgehen - nicht immer.
Blaisorblade
3
Ich würde +1 WENN die Antwort etwas über Klassen erwähnt, die Zeiger (ob automatisch, intelligent oder primitiv) auf dynamisch zugewiesenen (Heap-) Speicher enthalten, wobei der Kopierkonstruktor notwendigerweise tiefe Kopien ausführt. In solchen Fällen gibt es kein Argument, ++ i ist vielleicht eine Größenordnung effizienter als i ++. Der Schlüssel besteht darin, sich daran zu gewöhnen, Pre-Inkremente zu verwenden, wenn die Post-Inkrement-Semantik von Ihrem Algorithmus nicht tatsächlich benötigt wird, und Sie werden dann die Gewohnheit haben, Code zu schreiben, der sich von Natur aus für eine höhere Effizienz eignet, unabhängig davon, wie Nun, Ihr Compiler kann optimieren.
Phonetagger
64

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:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

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.

wilhelmtell
quelle
2
"Der Pre-Inkrement-Operator führt eine Datenabhängigkeit in den Code ein: Die CPU muss warten, bis die Inkrementierungsoperation abgeschlossen ist, bevor ihr Wert im Ausdruck verwendet werden kann. Bei einer CPU mit starker Pipeline führt dies zu einem Stillstand. Es besteht keine Datenabhängigkeit für den Post-Inkrement-Operator. " ( Game Engine Architecture (2. Ausgabe) ) Wenn die Kopie eines Post-Inkrements nicht rechenintensiv ist, kann sie das Pre-Inkrement dennoch übertreffen.
Matthias
Wie funktioniert das im Postfix-Code? C t(*this); ++(*this); return t;In der zweiten Zeile erhöhen Sie den Zeiger nach rechts. Wie wird also taktualisiert, wenn Sie dies erhöhen? Wurden die Werte nicht bereits kopiert t?
Rasen58
The operator++(int) function must create a copy.Nein ist es nicht. Nicht mehr Exemplare alsoperator++()
Severin Pappadeux
47

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

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}

O (n) Inkrement

Prüfung

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Ergebnisse

Ergebnisse (Zeitangaben in Sekunden) mit g ++ 4.5 auf einer virtuellen Maschine:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82

O (1) Inkrement

Prüfung

Nehmen wir nun die folgende Datei:

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Es macht nichts in der Inkrementierung. Dies simuliert den Fall, dass die Inkrementierung eine konstante Komplexität aufweist.

Ergebnisse

Die Ergebnisse variieren jetzt extrem:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90

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++sagt increment i, I am interested in the previous value, though.
  • ++isagt increment i, I am interested in the current valueoder increment 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.

Phresnel
quelle
1
Interessanter Test. Jetzt, fast zweieinhalb Jahre später, zeigen gcc 4.9 und Clang 3.4 einen ähnlichen Trend. Clang ist bei beiden etwas schneller, aber die Unterschiede zwischen Pre- und Postfix sind schlimmer als bei gcc.
Kauartikel Socken
Was ich wirklich gerne sehen würde, ist ein reales Beispiel, in dem ++ i / i ++ einen Unterschied macht. Macht es zum Beispiel einen Unterschied bei einem der Standard-Iteratoren?
Jakob Schou Jensen
@JakobSchouJensen: Diese waren eigentlich als Beispiele aus der realen Welt gedacht. Stellen Sie sich eine große Anwendung mit komplexen Baumstrukturen (z. B. kd-Bäume, Quad-Bäume) oder großen Containern vor, die in Ausdrucksvorlagen verwendet werden (um den Datendurchsatz auf SIMD-Hardware zu maximieren). Wenn es dort einen Unterschied macht, bin ich mir nicht sicher, warum man für bestimmte Fälle auf das Nachinkrement zurückgreifen würde, wenn dies semantisch nicht benötigt wird.
Sebastian Mach
@phresnel: Ich glaube nicht, dass Operator ++ in Ihrem Alltag eine Ausdrucksvorlage ist - haben Sie ein aktuelles Beispiel dafür? Die typische Verwendung von Operator ++ ist für Ganzzahlen und Iteratoren. Ich denke, es wäre interessant zu wissen, ob es einen Unterschied gibt (es gibt natürlich keinen Unterschied bei ganzen Zahlen - aber bei Iteratoren).
Jakob Schou Jensen
@JakobSchouJensen: Kein aktuelles Geschäftsbeispiel, aber einige Anwendungen, bei denen Sie Dinge zählen. Betrachten Sie für Iteratoren einen Raytracer, der im idiomatischen C ++ - Stil geschrieben ist, und Sie haben einen Iterator für die 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 Beispiel parent node, child node, indexund solche Sachen. Alles in allem ist meine Haltung, auch wenn es nur wenige Beispiele gibt, ...
Sebastian Mach
20

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:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

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:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

Daraus resultierte Folgendes:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

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.

James Sutherland
quelle
8
Sie haben vergessen, den wichtigen Punkt zu beachten, dass hier alles inline ist. Wenn die Definitionen der Operatoren nicht verfügbar sind, kann die im Outline-Code vorgenommene Kopie nicht vermieden werden. Mit Inlining ist das Optim ganz offensichtlich, also wird es jeder Compiler tun.
Blaisorblade
14

Im Google C ++ Style Guide heißt es:

Vorinkrement und Vorkrementierung

Verwenden Sie das Präfixformular (++ i) der Inkrement- und Dekrementoperatoren mit Iteratoren und anderen Vorlagenobjekten.

Definition: Wenn eine Variable inkrementiert (++ i oder i ++) oder dekrementiert (--i oder i--) wird und der Wert des Ausdrucks nicht verwendet wird, muss entschieden werden, ob vor- oder nachkrementiert (dekrementiert) werden soll.

Vorteile: Wenn der Rückgabewert ignoriert wird, ist das "Pre" -Formular (++ i) nie weniger effizient als das "Post" -Formular (i ++) und häufig effizienter. Dies liegt daran, dass nach dem Inkrementieren (oder Dekrementieren) eine Kopie von i erstellt werden muss. Dies ist der Wert des Ausdrucks. Wenn ich ein Iterator oder ein anderer nicht skalarer Typ bin, kann das Kopieren teuer sein. Da sich die beiden Inkrementtypen beim Ignorieren des Werts gleich verhalten, warum nicht einfach immer vorinkrementieren?

Nachteile: In C entwickelte sich die Tradition, Post-Inkrement zu verwenden, wenn der Ausdruckswert nicht verwendet wird, insbesondere in for-Schleifen. Einige finden Post-Inkrement leichter zu lesen, da das "Subjekt" (i) dem "Verb" (++) vorausgeht, genau wie in Englisch.

Entscheidung: Für einfache skalare (Nicht-Objekt-) Werte gibt es keinen Grund, eine Form zu bevorzugen, und wir erlauben dies auch. Verwenden Sie für Iteratoren und andere Vorlagentypen das Vorinkrement.

Martjno
quelle
1
"Entscheidung: Für einfache skalare (nicht objektbezogene) Werte gibt es keinen Grund, ein Formular zu bevorzugen, und wir erlauben dies auch. Verwenden Sie für Iteratoren und andere Vorlagentypen das Vorinkrement."
Nosredna
2
Eh, ... und was ist das für ein Ding?
Sebastian Mach
Der erwähnte Link in der Antwort ist derzeit defekt
Karol
4

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
4

@ Ketan

... wirft übersehene Details in Bezug auf Absicht und Leistung auf. Es gibt Zeiten, in denen wir iter ++ anstelle von ++ iter verwenden möchten.

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 forSchleifen). 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 ++iist das natürlicher, aber meine Erfahrung zeigt mir, dass ich in einer Minderheit bin und die Verwendung i++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 ++Ceinen logischeren Namen ein.

Motti
quelle
4
@Motti: (Scherz) Der C ++ - Name ist logisch, wenn Sie sich daran erinnern, dass Bjarne Stroustrup C ++ ihn ursprünglich als Pre-Compiler codiert hat, der ein C-Programm generiert. Daher hat C ++ einen alten C-Wert zurückgegeben. Oder es könnte sein, dass C ++ von Anfang an konzeptionell fehlerhaft ist.
Kriss
4
  1. ++ i - schneller ohne Rückgabewert
  2. i ++ - schneller mit dem Rückgabewert

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.

Hans Malherbe
quelle
3

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

0124816
quelle
3

Der Leistungsunterschied zwischen ++iund i++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, intals wäre es einstruct .

++ierhö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:

int& int::operator++() { 
     return *this += 1;
}

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:

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

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 structoder classbeteiligt 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.

DragonLord
quelle
1

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

Mike Dunlavey
quelle
1

@ 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

Mat Noguchi
quelle
1
Das ist nicht relevant. NRVO vermeidet die Notwendigkeit, t in "CC :: operator ++ (int)" zurück in den Aufrufer zu kopieren, aber i ++ kopiert weiterhin den alten Wert auf den Stapel des Aufrufers. Ohne NRVO erstellt i ++ zwei Kopien, eine zu t und eine zurück zum Anrufer.
Blaisorblade
0

Ein Grund, warum Sie ++ i auch bei integrierten Typen verwenden sollten, bei denen es keinen Leistungsvorteil gibt, besteht darin, sich eine gute Angewohnheit anzueignen.

Josh
quelle
3
Entschuldigung, aber das stört mich. Wer sagt, dass es eine "gute Angewohnheit" ist, wenn es fast nie darauf ankommt? Wenn die Leute es zu einem Teil ihrer Disziplin machen wollen, ist das in Ordnung, aber lassen Sie uns wichtige Gründe von Angelegenheiten des persönlichen Geschmacks unterscheiden.
Mike Dunlavey
@ MikeDunlavey ok, welche Seite benutzt du normalerweise, wenn es keine Rolle spielt? xD es ist entweder das eine oder das andere, nicht wahr? Das Post ++ (wenn Sie es mit der allgemeinen Bedeutung verwenden. Aktualisieren Sie es, geben Sie das alte zurück) ist ++ Pre (aktualisieren, zurückgeben) völlig unterlegen. Es gibt nie einen Grund, warum Sie weniger Leistung wünschen. In dem Fall, in dem Sie es später aktualisieren möchten, führt der Programmierer das Post ++ dann überhaupt nicht aus. Keine Zeitverschwendung beim Kopieren, wenn wir es bereits haben. Aktualisieren Sie es, nachdem wir es verwendet haben. dann die Compiler mit dem gesunden Menschenverstand, den Sie wollten.
Pfütze
@Puddle: Wenn ich das höre: "Es gibt nie einen Grund, warum Sie weniger Leistung wünschen" Ich weiß, ich höre "Penny Wise - Pound Dummkopf". Sie müssen die Größenordnungen kennen. Nur wenn dies mehr als 1% der Zeit ausmacht, sollten Sie darüber nachdenken. Wenn Sie darüber nachdenken, gibt es normalerweise millionenfach größere Probleme, die Sie nicht berücksichtigen, und dies macht Software viel langsamer, als es sein könnte.
Mike Dunlavey
@ MikeDunlavey erbrochenen Unsinn, um dein Ego zu befriedigen. Sie versuchen, wie ein weiser Mönch zu klingen, sagen aber nichts. die Größenordnungen ... wenn nur über 1% der Zeit, die Sie interessieren sollten ... xD absolutes Dribbeln. Wenn es ineffizient ist, lohnt es sich, darüber Bescheid zu wissen und es zu beheben. Wir sind hier, um genau aus diesem Grund darüber nachzudenken! Wir sind nicht besorgt darüber, wie viel wir aus diesem Wissen gewinnen können. und als ich sagte, du würdest nicht weniger Leistung wollen, mach weiter, erkläre dann ein verdammtes Szenario. MR WISE!
Pfütze
0

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:

#include <stdio.h>

int main()
{
    int a = 0;
    a++;
    int b = 0;
    ++b;
    return 0;
}

Stellen Sie die folgende Baugruppe her:

 0x0000000100000f24 <main+0>: push   %rbp
 0x0000000100000f25 <main+1>: mov    %rsp,%rbp
 0x0000000100000f28 <main+4>: movl   $0x0,-0x4(%rbp)
 0x0000000100000f2f <main+11>:    incl   -0x4(%rbp)
 0x0000000100000f32 <main+14>:    movl   $0x0,-0x8(%rbp)
 0x0000000100000f39 <main+21>:    incl   -0x8(%rbp)
 0x0000000100000f3c <main+24>:    mov    $0x0,%eax
 0x0000000100000f41 <main+29>:    leaveq 
 0x0000000100000f42 <main+30>:    retq

Sie sehen, dass es für a ++ und b ++ eine inkl. Mnemonik ist, also ist es die gleiche Operation;)

Geoffroy
quelle
Es ist C, während OP C ++ fragte. In C ist es dasselbe. In C ++ ist ++ i schneller; aufgrund seines Objekts. Einige Compiler optimieren jedoch möglicherweise den Post-Inkrement-Operator.
Wiggler Jtag
0

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.

Blaisorblade
quelle
Ich sehe, Sie arbeiten an einem Ph.D. mit Interesse an Compileroptimierung und solchen Dingen. Das ist großartig, aber vergessen Sie nicht, dass die Wissenschaft eine Echokammer ist und der gesunde
Mike Dunlavey
Ich fand es nie ++iärgerlicher als i++(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"
Sebastian Mach
strncpydiente 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.
MM
@MattMcNabb: Waren Dateinamen mit 8 Zeichen nicht exklusiv für MS-DOS? C wurde mit Unix erfunden. Wie auch immer, selbst wenn strncpy einen Punkt hatte, war das Fehlen von strlcpy nicht vollständig gerechtfertigt: Selbst das ursprüngliche C hatte Arrays, die Sie nicht überlaufen sollten, die strlcpy benötigten; Allenfalls fehlten nur Angreifer, die die Fehler ausnutzen wollten. Aber man kann nicht sagen, dass die Vorhersage dieses Problems trivial war. Wenn ich also meinen Beitrag umschreiben würde, würde ich nicht den gleichen Ton verwenden.
Blaisorblade
@Blaisorblade: Soweit ich mich erinnere, waren frühe UNIX-Dateinamen auf 14 Zeichen beschränkt. Das Fehlen von strlcpy()wurde durch die Tatsache gerechtfertigt, dass es noch nicht erfunden worden war.
Keith Thompson
-1

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

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

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.

Severin Pappadeux
quelle
-5

++i ist schneller als i++ weil es keine alte Kopie des Wertes zurückgibt.

Es ist auch intuitiver:

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

In diesem C-Beispiel wird "02" anstelle der erwarteten "12" gedruckt:

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

Gleiches gilt für C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
Cees Timmerman
quelle