Frage

Wir haben die Frage wird ein Leistungsunterschied zwischen i++ und ++i in C ?

Was ist die Antwort für C ++?

War es hilfreich?

Lösung

[Zusammenfassung:. Verwenden ++i, wenn Sie nicht einen bestimmten Grund haben i++ verwenden]

Für C ++, die Antwort ist ein wenig komplizierter.

Wenn i ein einfacher Typ ist (nicht eine Instanz einer C ++ Klasse), dann ist die Antwort für C gegeben ( "Nein, es ist kein Unterschied in der Leistung ") hält, da der Compiler den Code generiert.

Wenn jedoch i eine Instanz einer C ++ Klasse ist, dann i++ und ++i werden die Anrufe an eine der operator++ Funktionen machen. Hier ist ein Standard-Paar 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 zu erzeugen, sondern nur eine operator++ Funktion aufruft, gibt es keine Möglichkeit, die tmp Variable und den damit verbundenen Copykonstruktor zu optimieren entfernt. Wenn der Copykonstruktor teuer ist, dann kann dies eine erhebliche Auswirkung auf die Leistung haben.

Andere Tipps

Ja. Es ist.

Der Operator ++ kann oder kann nicht als eine Funktion definiert werden. Für primitive Typen (int, double, ...) die Betreiber integriert, so dass der Compiler wird wahrscheinlich in der Lage sein, Ihren Code zu optimieren. Aber im Fall eines Objekts, das die Operator ++ Dinge anders definiert.

Der Operator ++ (int) Funktion muss eine Kopie erstellen. Das ist, weil Postfix ++ erwartet wird, einen anderen Wert zurück als das, was gilt: es muss seinen Wert in einer temporären Variable halten, seinen Wert erhöhen und die temp zurück. Im Fall des Operators ++ (), Präfix ++ gibt es keine Notwendigkeit, eine Kopie zu erstellen. Das Objekt selbst erhöhen kann und dann einfach zurückgeben selbst

Hier ist eine Darstellung 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, rufen Sie Operator ++ (int) eine Kopie erstellen müssen, und der Compiler kann etwas dagegen tun. Wenn sie die Wahl, verwenden Operator ++ (); Auf diese Weise sparen Sie nicht über eine Kopie. Es könnte im Fall von vielen Schritten von Bedeutung sein (großer Schleife?) Und / oder große Objekte.

Hier ist ein Benchmark für den Fall, wenn erhöhten Prüfer in verschiedenen Übersetzungseinheiten sind. Compiler mit g ++ 4.5.

für jetzt die Stilfragen ignorieren

// 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) Schritt

Test

// 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 sind 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) Zuwachs

Test

Lassen Sie uns nun die folgende Datei übernehmen:

// 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 tut nichts in der Inkrementierung. Dies simuliert den Fall, wenn Inkrementierung konstante Komplexität hat.

Ergebnisse

Ergebnisse jetzt variieren 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

Performance-weise

Wenn Sie nicht über den vorherigen Wert benötigen, macht es sich zur Gewohnheit Prä-Inkrement zu verwenden. Seien Sie konsequent auch bei eingebauten Typen, werden Sie sich daran gewöhnen und nicht laufen Gefahr von unnötigen Leistungsverlust leiden, wenn Sie jemals einen eingebauten Typ mit einem benutzerdefinierten Typ ersetzen.

Semantic-weise

  • i++ sagt increment i, I am interested in the previous value, though.
  • ++i sagt increment i, I am interested in the current value oder increment i, no interest in the previous value. Auch hier werden Sie sich daran gewöhnen, auch wenn Sie jetzt nicht richtig sind.

Knuth.

Vorzeitige Optimierung ist die Wurzel aller Übel. Wie vorzeitige Pessimierung.

Es ist nicht ganz richtig zu sagen, dass der Compiler kann die temporäre Variable Kopie im Postfix Fall nicht optimieren entfernt. Ein kurzer Test mit VC zeigt, dass es zumindest in bestimmten Fällen das tun.

In dem folgenden Beispiel erzeugt der Code ist identisch für Präfix und Postfix, zum Beispiel:

#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());
}

Ob Sie ++ testFoo oder testFoo ++ tun, werden Sie immer noch die gleiche resultierende Code. In der Tat, ohne die Zählung in vom Benutzer zu lesen, bekam der Optimierer das Ganze bis auf eine Konstante. So folgt aus:

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

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

ergab folgende:

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

So, während es ist sicherlich der Fall, dass die Postfix-Version langsamer sein könnte, kann es gut sein, dass der Optimierer gut genug sein wird loszuwerden, die temporären Kopie zu erhalten, wenn Sie sie nicht verwenden.

Die Google C ++ Style Guide sagt :

  

Prä-und Predecrement

     

Mit Präfix Form (++ i) der Inkrement- und Dekrementoperatoren mit   Iteratoren und andere Vorlagenobjekte.

     

Definition: Wenn eine Variable erhöht (++ i oder i ++) oder erniedrigt (--i oder   i--) und der Wert des Ausdrucks nicht benutzt wird, muss man sich entscheiden,   Vorinkrement (Abnahme) oder Postinkrement (Abnahme), ob auf.

     

Vorteile: Wenn der Rückgabewert ignoriert wird, die "pre" Form (++ i) ist nie weniger   effizienter als die „post“ Form (i ++) und ist oft effizienter.   Dies liegt daran, Post-Inkrement (oder Dekrement) eine Kopie i erfordert   gemacht werden, was der Wert des Ausdrucks ist. Wenn i ist ein Iterator oder   andere nicht-skalare Typ, Kopieren i teuer sein könnte. Da die beiden   Arten von Inkrementen gleich verhalten, wenn der Wert ignoriert wird, warum nicht   nur immer Prä-Inkrement?

     

Nachteile: Die Tradition entwickelte, in C, die Verwendung von Post-Inkrement, wenn die   Expressionswert nicht, insbesondere in für die Schlaufen verwendet. einige finden   Nachinkrement leichter zu lesen, da das „Subjekt“ (i) steht vor dem   "Verb" (++), genau wie in Englisch.

     

Entscheidung: Für einfache skalare (nicht-Objekt) Werte gibt es keinen Grund, warum man bevorzugen   bilden und wir zulassen, dass entweder. Für Iteratoren und andere Template-Typen, Verwendung   Prä-Inkrement.

Ich mag sehr kurze Zeit einen hervorragenden Beitrag von Andrew Koenig auf Kodex Diskussion hinweisen.

http://dobbscodetalk.com/index. php? option = com_myblog & show = Efficiency-versus-intent.html & Itemid = 29

In unserem Unternehmen auch nutzen wir Konvention von ++ iter für Konsistenz und Leistung, soweit anwendbar. Aber Andrew wirft über-sah Detail in Bezug auf Absicht vs Leistung. Es gibt Zeiten, in denen wir iter ++ statt ++ iter verwenden möchten.

Also, zuerst Ihre Absicht entscheiden und ob vor oder nach dem dann mit vorge geht nicht Angelegenheit, wie es durch die Vermeidung Schaffung zusätzlichen Gegenstandes einig Performance-Vorteil haben wird, und wirft es.

@Ketan

  

... wirft über-sah Detail in Bezug auf Absicht vs Leistung. Es gibt Zeiten, in denen wir iter ++ statt ++ iter verwenden möchten.

Offensichtlich posten und Prä-Inkrement unterschiedliche Semantik und ich bin sicher, dass sie alle einig, dass, wenn das Ergebnis verwendet wird, sollten Sie den entsprechenden Operator verwenden. Ich denke, die Frage ist, was sollte man tun, wenn das Ergebnis (wie in for Schleifen) werden verworfen. Die Antwort auf diese Frage (IMHO) ist, dass, da die Leistungsaspekte zu vernachlässigen besten sind, sollten Sie das tun, was natürlicher ist. Für mich ist ++i natürlichere aber meine Erfahrung sagt mir, dass ich in der Minderheit bin und i++ verwendet, wird weniger Metall Kopf verursachen für die meisten Code Menschen zu lesen.

Nach allem, was ist der Grund, die Sprache wird nicht aufgerufen " ++C ". [*]

[*] Insert obligatorische Diskussion über ++C ein logischer Name sein.

Mark: Ich wollte nur, dass die Betreiber darauf hin ++ 's sind gute Kandidaten zu inlined, und wenn der Compiler dies zu tun wählt, wird die redundante Kopie in den meisten Fällen verzichtet werden. (Z POD-Typen, die in der Regel Iteratoren sind.)

Das heißt, es ist immer noch besser Stil ++ iter in den meisten Fällen zu verwenden. : -)

Der Performance-Unterschied zwischen ++i und i++ wird deutlicher, wenn Sie von Operatoren als Value-Rückkehr Funktionen denken und wie sie umgesetzt werden. Um es einfacher zu verstehen, was passiert, werden die folgenden Code Beispiele verwenden int, als ob es ein struct waren.

++i erhöht die Variable und gibt das Ergebnis. Dies kann an Ort und Stelle und mit minimaler CPU-Zeit durchgeführt werden und erfordert nur eine Zeile Code in vielen Fällen:

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

Aber das gleiche kann nicht von i++ gesagt werden.

Post-Inkrementierung, i++, wird oft als Rückkehr des ursprünglichen Wertes gesehen vor erhöht wird. Allerdings eine Funktion kann nur ein Ergebnis zurück, wenn es fertig ist . Als Ergebnis wird es notwendig, eine Kopie der Variablen den ursprünglichen Wert enthält, erhöht die Variable, dann wieder die Kopie hält den ursprünglichen Wert zu erstellen:

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

Wenn es keinen funktionalen Unterschied zwischen Prä-Inkrement und Post-Inkrement ist, kann der Compiler durchführen Optimierung, so dass es keinen Unterschied in der Leistung zwischen den beiden. Wenn jedoch ein Verbunddatentyp wie ein struct oder class beteiligt ist, wird der Kopierkonstruktor auf Nachinkrement aufgerufen werden, und es wird nicht möglich sein, diese Optimierung durchzuführen, wenn eine tiefe Kopie benötigt wird. Als solches allgemeine Prä-Inkrement schneller und erfordert weniger Speicher als Post-Inkrement.

  1. ++ i - schneller nicht mit der Rückgabewert
  2. i ++ - schneller mit der Rückgabewert

Wenn nicht mit der Rückgabewert der Compiler garantiert keine temporäre im Falle von verwenden ++ i . Nicht garantiert schneller sein, aber garantiert nicht langsamer sein.

Wenn mit der Rückgabewert i ++ kann der Prozessor sowohl schieben die  erhöhen und die linke Seite in die Pipeline, da sie sich nicht ab. ++ i kann die Pipeline an, da der Prozessor nicht auf die linke Seite, bis die vorge Inkrementbetrieb den ganzen Weg durch hat schlängelte beginnen. Auch hier wird eine Pipeline Stall nicht gewährleistet, da der Prozessor andere nützliche Dinge in bleiben finden.

Eine der Grund, warum sollten Sie verwenden ++ i auch auf eingebauten Typen, wo es keinen Performance-Vorteil ist, ist eine gute Gewohnheit für sich selbst zu schaffen.

@ Mark: Ich meine Antwort gelöscht, weil es ein bisschen Flip war, und verdient einen Downvote für das allein. Ich glaube tatsächlich, es ist eine gute Frage in dem Sinne, dass sie fragt, was auf den Köpfen von vielen Menschen ist.

Die übliche Antwort ist, dass ++ i ist schneller als i ++, und kein Zweifel, es ist, aber die wichtigere Frage ist „wenn sollten Sie darauf?“

Wenn der Anteil der CPU-Zeit in Inkrementieren Iteratoren ausgegeben wird, weniger als 10%, dann können Sie nicht.

Wenn der Anteil der CPU-Zeit verbrachte in Iteratoren Erhöhen größer als 10%, können Sie sehen, bei den Aussagen, dass das Iterieren tun. Sehen Sie, wenn Sie nur ganze Zahlen erhöhen könnte, anstatt Iteratoren verwenden. Die Chancen stehen Sie könnten, und während es in gewissem Sinne sein kann, weniger wünschenswert, die Chancen sind ziemlich gut Sie im Wesentlichen die ganze Zeit in diesen Iteratoren verbrachte sparen.

Ich habe ein Beispiel gesehen, wo die Iterator-Inkrementierung wurde weit über 90% der Zeit in Anspruch. In diesem Fall geht reduzierte Ausführungszeit ganzzahligen Inkrementierung durch im wesentlichen dieses Betrag. (D.h. besser als 10-fach speedup)

Die beabsichtigte Frage war über, wenn das Ergebnis nicht verwendet wird (das ist klar von der Frage für C). Kann jemand da die Frage, dieses Problem zu beheben ist „Community Wiki“?

Über vorzeitige Optimierungen wird Knuth oft zitiert. Stimmt. aber Donald Knuth würde nie mit, dass der schrecklichen Code verteidigen, die Sie in diesen Tagen zu sehen. Schon mal ein = b + c unter Java ganzen Zahlen gesehen (nicht int)? Das entspricht 3 Boxen / Unboxing-Konvertierungen. Vermeiden Sachen wie das ist wichtig. Und unnütz Schreiben i ++ statt ++ i die gleichen Fehler. EDIT:. Wie phresnel setzt es schön in einem Kommentar, das als zusammengefasst werden kann „vorzeitige Optimierung ist böse, wie vorzeitige Pessimierung“

Auch die Tatsache, dass die Menschen zu i mehr verwendet werden ++ ein unglücklichen C Vermächtnis ist, verursacht durch einen konzeptuellen Fehler von K & R (wenn Sie der Absicht Argumente folgen, das ist eine logische Schlussfolgerung, und die Verteidigung K & R weil sie K & R ist bedeutungslos, sie sind groß, aber sie sind nicht groß, wie Sprachdesigner, unzählige Fehler in der C-Design gibt, von gets () zu strcpy (), zum strncpy () API (den strlcpy () API gehabt haben sollte, da Tag 1)).

Btw, ich bin einer von denen nicht genug, um C ++ verwendet ++ i ärgerlich zu finden, zu lesen. Dennoch benutze ich, da ich zur Kenntnis, dass es richtig ist.

@wilhelmtell

Der Compiler kann die temporäre elide. Verbatim aus dem anderen Thread:

Die C ++ Compiler ist erlaubt Stack basierten Provisorien zu beseitigen, auch wenn dabei das Verhalten des Programms so ändert. MSDN-Link für VC 8:

http://msdn.microsoft.com/ en-us / library / ms364057 (VS.80) aspx

Zeit zu schaffen, Leute mit Perlen der Weisheit;) - es ist einfacher Trick C ++ postfix Schritt verhalten so ziemlich die gleichen wie Präfix Schritt zu machen (Phantasie dies für mich, aber die Sägen es auch in anderem Code, so dass ich ist nicht allein).

Grundsätzlich Trick ist Helfer-Klasse verwendet Schritt nach der Rückkehr zu verschieben, und RAII kommt zu retten

#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 ist für einige schwere benutzerdefinierte Iteratoren Code, und es verkürzt sich der Laufzeit. Kosten der Vorsilbe vs Postfix ist eine Referenz jetzt, und wenn dieser Brauch Betreiber tun schwer bewegen, Präfix und Postfix ergaben die gleiche Laufzeit für mich.

Beide sind so schnell;) Wenn Sie wollen, dass es die gleiche Berechnung für den Prozessor ist, es ist nur die Reihenfolge, in der sie durchgeführt wird, dass sie unterscheiden.

Zum Beispiel der folgende Code:

#include <stdio.h>

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

Erstellen Sie die folgende Montage:

 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 für einen ++ und b ++ es ist eine incl mnemonic, so dass es die gleiche Operation ist;)

Wenn Sie schreiben i++ Sie die Compiler sagen zu erhöhen, nachdem sie diese Linie oder Schleife beendet ist.

++i ist ein wenig anders als i++. In i++ erhöhen Sie, nachdem Sie die Schleife beenden, aber ++i Sie direkt vor der Schleife beendet erhöhen.

++i ist schneller als i++, weil es nicht eine 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 

Dieses C Beispiel prints "02" statt der "12" Sie erwarten:

#include <stdio.h>

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

Das Gleiche gilt für C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top