warum ++i performanter als i++



  • Hallo,

    es heißt ja immer, ++i sei performanter.
    Warum in aller Welt sehe ich davon auf ASM Level nichts mehr davon?

    int x=0,y=0;
    
    	y=x++;
    	y=++x;
    
    y=x++;
    0137187C 8B 45 F8         mov         eax,dword ptr [x] 
    0137187F 89 45 EC         mov         dword ptr [y],eax 
    01371882 8B 4D F8         mov         ecx,dword ptr [x] 
    01371885 83 C1 01         add         ecx,1 
    01371888 89 4D F8         mov         dword ptr [x],ecx 
    	y=++x;
    0137188B 8B 45 F8         mov         eax,dword ptr [x] 
    0137188E 83 C0 01         add         eax,1 
    01371891 89 45 F8         mov         dword ptr [x],eax 
    01371894 8B 4D F8         mov         ecx,dword ptr [x] 
    01371897 89 4D EC         mov         dword ptr [y],ecx
    


  • sadasdasd schrieb:

    Hallo,

    es heißt ja immer, ++i sei performanter.
    Warum in aller Welt sehe ich davon auf ASM Level nichts mehr davon?

    int x=0,y=0;
    
    	y=x++;
    	y=++x;
    
    y=x++;
    0137187C 8B 45 F8         mov         eax,dword ptr [x] 
    0137187F 89 45 EC         mov         dword ptr [y],eax 
    01371882 8B 4D F8         mov         ecx,dword ptr [x] 
    01371885 83 C1 01         add         ecx,1 
    01371888 89 4D F8         mov         dword ptr [x],ecx 
    	y=++x;
    0137188B 8B 45 F8         mov         eax,dword ptr [x] 
    0137188E 83 C0 01         add         eax,1 
    01371891 89 45 F8         mov         dword ptr [x],eax 
    01371894 8B 4D F8         mov         ecx,dword ptr [x] 
    01371897 89 4D EC         mov         dword ptr [y],ecx
    

    Bei int war es schon immer gleich. Je nach Schwäche des Compilers und Pipelines im Prozessor sogar mit einer Zendenz zum i++.
    Dein Compiler sollte inzwischen so gut optimieren können, daß es iht vollkommen Wurst ist, ob Du ++i, i++, i+=1, i=i+1 oder gar i-=-1 schreibst.

    Aber probier das mal mit Iteratoren, die nicht winzig sind oder mit BigIntergers mit tauzsenden von Stellen.


  • Mod

    Warum in aller Welt sehe ich davon auf ASM Level nichts mehr davon?

    Weil der Compiler nicht doof ist.

    Relevant wird das, wenn als Datentyp mal nicht int vorliegt, sondern ein komplexes Klassenobjekt. Dann kann der Compiler keine Tricks durch das Jonglieren mit Registern benutzen.

    edit: Zu langsam.



  • Es ist fast nie schneller. Das ist nur die Theorie.

    In der Praxis optimiert der Compiler das (meistens) immer sehr gut, sodass es kein Stück langsamer mehr ist - wie in deinem Fall, oder auch bei for(;;i++) o.ä.

    Edit: Das ist natürlich abhängig davon, was i ist, wie die anderen sagen. 😃
    Edit²: So langsam? 😮



  • sadasdasd schrieb:

    es heißt ja immer, ++i sei performanter [als i++].

    Heißt es das? nee ... aber es könnte performanter sein in Abhängigkeit dessen, was i ist.

    Beispiel:

    struct foo
    {
      int bar;
      foo& operator++()    {               ++bar; return *this;} // präfix  ++
      foo  operator++(int) {foo old=*this; ++bar; return old;}   // postfix ++
    };
    

    wie du siehst, wird beim postfix++ eine kopie erzeugt und die kopie zurückgegeben, während beim ++präfix nur eine Referenz auf das aktuelle Objekt zurückgegeben wird. Da passiert also unterschiedlilch viel.

    Das kann dann schon einen Unterschied machen, zum Beispiel bei einem shared_container_iterator , wo im Falle eines postfix++ dann unnötig ein shared_ptr kopiert würde, wobei dann unnötig der Referenzzähler inkrementiert würde, nur, damit die temporäre Kopie des alten Iteratorzustands beim Semikolon wieder zerstört wird.



  • ok bei int ists also gleich.

    werds mal mit großen typen probieren.



  • Es sollte ein Objekt sein, bei dem der Kopierkonstruktor nicht simpel ist.
    Dann merkst du einen großen Unterschied.

    Bsp:

    class chars
    {
    private:
        std::string _s;
        char _c;
    
    public:
        chars(char c)
            : _c(c)
        { }
    
        chars& operator++()
        {
            _s.push_back(_c);
        }
    
        chars operator++(int)
        {
            chars copy = *this;
            ++(*this);
            return copy;
        }
    };
    
    int main()
    {
        chars a('a');
        for(int i = 0; i < 5000000; ++i)
            ++a;
    
        for(int i = 0; i < 5000000; ++i)
            a++;
    }
    

    Ist aber ein reichlich blödes Beispiel.



  • es heißt ja immer, ++i sei performanter.

    Quatsch. Schau dir doch mal bitte an: http://isocpp.org/blog/2012/12/three-optimization-tips-alexandrescu

    In Kombination mit Arrays kann Zugriff und Inkrement in der Pipeline parallel/out of order abgearbeitet werden:

    array[i++] = 54
    


  • sadasdasd schrieb:

    es heißt ja immer, ++i sei performanter.

    Ich bin kein Freund von "Premature Optimization", aber auch ein Feind davon, Code potentiell langsamer zu machen, wenn es kein Aufwand bedeutet, potentiell schnelleren Code zu schreiben. Der Mensch ist zudem ein Gewohnheitstier, daher sollte man sich in dem Fall einfach das potentiell bessere angewöhnen.



  • das potentiell bessere angewöhnen

    Und das waere fuer ints ?



  • knivil schrieb:

    es heißt ja immer, ++i sei performanter.

    Quatsch. Schau dir doch mal bitte an: http://isocpp.org/blog/2012/12/three-optimization-tips-alexandrescu

    In Kombination mit Arrays kann Zugriff und Inkrement in der Pipeline parallel/out of order abgearbeitet werden:

    array[i++] = 54
    

    Da wird der Compiler doch selber drauf kommen?



  • Ich mag i++ nicht. Immer wenn ich es sehe, frage ich mir, wozu brauchst du hier den alten Wert? weißt du nicht was Post-Inc/Dec macht oder willst du mich nur in die Irre führen?... Außerdem find ich Post-Inc/Dec inkonsistent.



  • Ethon schrieb:

    Da wird der Compiler doch selber drauf kommen?

    Wie meinst du das? Auf was soll der Kompiler kommen?

    Ich mag i++ nicht.

    Ja, tolle Begruendung.

    Immer wenn ich es sehe, frage ich mir, wozu brauchst du hier den alten Wert?

    Nun, schon mal Standardimplementierungen von memcpy/strcpy gesehen?


  • Mod

    knivil schrieb:

    In Kombination mit Arrays kann Zugriff und Inkrement in der Pipeline parallel/out of order abgearbeitet werden:

    array[i++] = 54
    

    Ist bei Präinkrement nicht anders, sofern der Prozessor über geeignete Adressierungsmodi verfügt.



  • knivil schrieb:

    Ethon schrieb:

    Da wird der Compiler doch selber drauf kommen?

    Wie meinst du das? Auf was soll der Kompiler kommen?

    Liege ich gerade falsch oder geht es darum dass der Array-Zugriff mit Postinkrement schneller ist?
    Falls das stimmt, wird der Compiler doch wohl schlau genug sein bzw die CPU hinreichend kennen und die performantere Methode wählen, wenn es keinen Einfluss auf das Programmverhalten hat.



  • Ist bei Präinkrement nicht anders, sofern der Prozessor über geeignete Adressierungsmodi verfügt.

    Also bei

    array[++i] = 45
    

    Soll er i inkrementieren und parallel an die Adresse mit dem neuen i schreiben?

    wird der Compiler doch wohl schlau genug sein

    Was schneller ist, weiss ich nicht, da ich nicht gemessen habe. Aber prinzipiell sollte der Compiler schon den Quelltext beruecksichtigen, den ich hinschreibe.

    PS: Das Video dient als Referenz. D.h. ich habe nichts selbst gemessen oder ausprobiert.



  • knivil schrieb:

    Ich sehe schon, dass niemand das Video sich angesehen hat. Anscheinend wars bei Facebook anders.

    Ich hab mir die verlinkten Slides des Vortrags angeschaut. Der Vortrag ist mir gerade zu lang. 😉



  • Ethon schrieb:

    Ich hab mir die verlinkten Slides des Vortrags angeschaut. Der Vortrag ist mir gerade zu lang. 😉

    Ja, das ist er.


  • Mod

    knivil schrieb:

    Ist bei Präinkrement nicht anders, sofern der Prozessor über geeignete Adressierungsmodi verfügt.

    Also bei

    array[++i] = 45
    

    Soll er i inkrementieren und parallel an die Adresse mit dem neuen i schreiben?

    Der Schreibzugriff hängt nur scheinbar von der Inkrementierung von i ab.

    array[++i]
    

    ist

    *(array + ++i)
    

    ist

    *(array + (i+=1))
    

    ist
    *(array + iold + 1) und inew = iold + 1
    ist
    *((array + 1) + iold) und inew = iold + 1
    sofern array statische Lebensdauer hat, kann die Erhöhung bereits beim Compilieren direkt berücksichtigt werden, ansonsten helfen Adressierungsmodi mit festem Displacement, über die praktisch jeder moderne Prozessor verfügt.



  • knivil schrieb:

    Aber prinzipiell sollte der Compiler schon den Quelltext beruecksichtigen, den ich hinschreibe.

    Hmm, wenn ich

    if(x % 2 == 0)
    

    schreibe, der Compiler aber weiß dass meine CPU ein AND schneller ausführt als ein MOD und daraus

    if(x & 1 == 0)
    

    macht, dann bin ich aber froh.

    Ist imho das Gleiche wie bei den unterschiedlichen Inkrementierungsoperatoren.



  • Ethon schrieb:

    Ist imho das Gleiche wie bei den unterschiedlichen Inkrementierungsoperatoren.

    Nein, weil bei deinem Beispiel keine Seiteneffekte auftreten.

    kann die Erhöhung bereits beim Compilieren direkt berücksichtigt werden

    Nun, ich bin mir unsicher, ob der Compiler beispielsweise array einfach mit einem anderen Wert initialisiert. Und eine Zeile Code ist auch ein schlechtes Beispiel.

    Adressierungsmodi mit festem Displacement

    Meinst du, dass einfach eine Konstante in den Befehl mitkodiert wird? Kann mir gerade nicht so viel drunter vorstellen.


Anmelden zum Antworten