Verschachtelte If-Abfragen oder doch abbrechen?



  • Klingt sehr stark nach Mikrooptimierung.
    Sagen wir es so: Auch wenn es einen Unterschied macht, musst du so etwas nicht beachten.

    Mach das, was logisch in den Code passt.

    Alles andere kann man sowieso nicht allgemeingültig beantworten, sondern ist abhängig vom Compiler und dessen Optimierungen.

    Falls du in die Verlegenheit kommst deinen Code zu optimieren, dann wird das zu 99.99% nicht an dieser Stelle liegen und falls doch, dann hättest du das ja schnell geändert.



  • Hi.
    Dass man die Optimierungen nicht bemerken wird ist mir klar, nur es interessierte mich gerade.

    Danke!

    Gruß



  • Warum compiliert es keiner, um mal den ASM-Code anzuschauen?



  • volkard schrieb:

    Warum compiliert es keiner, um mal den ASM-Code anzuschauen?

    Weil das die Aufgabe von theliquidwave, wenn er sich dafür interessiert. Das es Compilerabhängig ist, was da produziert wird habe ich ja bereits gesagt.



  • Ich kann mir da nur zwei Szenarien vorstellen (Pseudo-Code).

    Im ersten Fall:

    JumpIfGreater L_IfEnd
    Return
    :L_IfEnd
    [...Code...]
    

    Im zweiten Fall:

    JumpIfSmaller L_IfEnd
    [...Code...]
    :L_IfEnd
    Return
    

    Sogesehen müsste die erste Variante schneller sein, da kein Spruch erforderlich ist. Andererseits könnte der Compiler die Situation auch anders lösen. Das ganze dürfte sich maximal um eine Operation unterscheiden. Ich gehe aber davon aus, dass der Compiler sowas selbstständig optimiert.



  • Gibt ja auch noch so spaßige Sachen wie Branch Prediction in der CPU, die man in der Praxis auch berücksichtigen muss.

    Besser ist das, was leichter zu lesen ist. In Sachen Performance kann man keine allgemein gültige Aussage machen.



  • theliquidwave schrieb:

    Was ist besser?

    Vielleicht ist die richtige Wahl des Datentypen von x besser. Ist x unsigned, so wird "Macro-fusion" der betroffenen Befehle in der Intel CPU aktiv, was angeblich perfomanter sein soll (s. ähnliche Diskussion hier http://www.c-plusplus.net/forum/viewtopic-var-t-is-252326-and-postdays-is-0-and-postorder-is-asc-and-start-is-60.html).
    D.h. so was wäre wahrscheinlich besser:

    unsigned int x;
    
        ...
    
        if (x != 0u)
        {
            ...
        }
    }
    

    Also alles unsigned, auch die Null (0u und nicht einfach 0)...
    Ich habe es natürlich so nicht getestet und es könnte sein, dass es alles Quatsch ist.



  • #include <iostream>
    #include <ctime>
    
    int x=std::time(0);
    
    void bla() 
    {
    #if 1
        if (x < 1) 
        { 
            return; 
        } 
        std::cout<<"";
    #else
        if (x > 0) 
        { 
            std::cout<<"";
        } 
    #endif
    } 
    
    int main() 
    { 
        bla();
    }
    

    gcc version 4.4.0 (GCC)
    g++ -O3 -march=native --save-temps main.cpp

    call	___main
    	movl	_x, %eax
    	testl	%eax, %eax
    	jle	L6
    	movl	$0, 8(%esp)
    	movl	$LC0, 4(%esp)
    	movl	$__ZSt4cout, (%esp)
    	call	__ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    L6:
    	xorl	%eax, %eax
    	leave
    	ret
    

    gegen

    call	___main
    	movl	_x, %eax
    	testl	%eax, %eax
    	jle	L6
    	movl	$0, 8(%esp)
    	movl	$LC0, 4(%esp)
    	movl	$__ZSt4cout, (%esp)
    	call	__ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i
    L6:
    	xorl	%eax, %eax
    	leave
    	ret
    


  • theliquidwave schrieb:

    Hi.
    Dass man die Optimierungen nicht bemerken wird ist mir klar, nur es interessierte mich gerade.

    Danke!

    Gruß

    Zunächst gehen wir mal durch die IDE in den Compiler. Der ist eigentlich dazu ständig solche Optimierungen durchzuführen, er wird schon wissen, ob "x >= 1" oder "x > 0" schneller sein wird. Oder ob das abbrechen am Anfang der Funktion technisch sinnvoll ist oder nicht. Das ist auf Quelltext-Ebene eher eine Frage des Aussehen (oder Unternehmensvorschiften).

    Schlussendlich hängt es vom Prozessor ab auf dem die Bits laufen. Soweit ich weiß, gibt es bei den allermeisten Befehlssätzen auf den CPU allgemein ein JUMPIFN und ein JUMPIFZ also wenn ein Register negativ ist oder 0. Also x == 0 und x < 0 sind dann ein einziger Befehl. (Abgesehn von Positions des Wertes ermitteln, Wertpositions Offset relativ zur Klasse bstimmen, aus dem Speicher laden, etc..)
    Eigentlich kann dir das aber egal sein, denn, wenn du eine (ungecachte) Speicheroperation durchführst ist das garantiert (selbst ohne Pipeline-Hazards) langsamer als etliche "if"-Befehle. Wie schon von anderen gesagt, solltest du solche "Kleinigkeiten" immer den Compiler überlassen, er weiß' schon was er macht.



  • Danke an Alle.
    @ abc.w: Man kann's auch übertreiben 😃

    Gruß



  • abc.w schrieb:

    Vielleicht ist die richtige Wahl des Datentypen von x besser. Ist x unsigned, so wird "Macro-fusion" ...

    Mit unsigned wird statt jle nur je gemacht.
    Dein Macro Fusion sagt, daß test lieber zu nehmen ist als cmp. Ist hier eh der Fall. Und nur kurzzeitwissen, bis Intel cmp nachgerüstet hat, nehme ich an.



  • volkard schrieb:

    ... Dein Macro Fusion sagt, daß test lieber zu nehmen ist als cmp. Ist hier eh der Fall....

    Ich habe jetzt echt eine Weile gebraucht, um zu kapieren, warum der Compiler in deinem Testprogramm test mit jle verwendet... Eigentlich trivial, es liegt ja an den Bedingungen, einmal (x < 1) und einmal (x > 0). In diesem Fall kann man ja test verwenden, sozusagen mit Null vergleichen, und dann jle für kleiner gleich Null Abfrage. Laut Intel-Doku funktioniert Macro-fusion bei test mit allen bedingten Sprüngen. Das ist ok.
    Ändert man aber in der Funktion die Bedingungen, z.B. einmal (x < 2) und einmal (x > 1), muss der Compiler cmp generieren:

    080486c0 <bla()>:
     80486c0:	55                   	push   %ebp
     80486c1:	89 e5                	mov    %esp,%ebp
     80486c3:	83 ec 18             	sub    $0x18,%esp
     80486c6:	83 3d d4 a0 04 08 01 	cmpl   $0x1,0x804a0d4
     80486cd:	7e 1c                	jle    80486eb <bla()+0x2b>
     80486cf:	c7 44 24 08 07 00 00 	movl   $0x7,0x8(%esp)
     80486d6:	00 
     80486d7:	c7 44 24 04 3c 89 04 	movl   $0x804893c,0x4(%esp)
     80486de:	08 
     80486df:	c7 04 24 40 a0 04 08 	movl   $0x804a040,(%esp)
     80486e6:	e8 c9 fe ff ff       	call   80485b4 <std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, int)@plt>
     80486eb:	c9                   	leave  
     80486ec:	c3                   	ret
    

    Folge: keine Macro-fusion, wegen int x 🙂

    Mit unsigned Version:

    80486c0 <bla()>:
     80486c0:	55                   	push   %ebp
     80486c1:	89 e5                	mov    %esp,%ebp
     80486c3:	83 ec 18             	sub    $0x18,%esp
     80486c6:	83 3d d4 a0 04 08 01 	cmpl   $0x1,0x804a0d4
     80486cd:	76 1c                	jbe    80486eb <bla()+0x2b>
     80486cf:	c7 44 24 08 07 00 00 	movl   $0x7,0x8(%esp)
     80486d6:	00 
     80486d7:	c7 44 24 04 bc 88 04 	movl   $0x80488bc,0x4(%esp)
     80486de:	08 
     80486df:	c7 04 24 40 a0 04 08 	movl   $0x804a040,(%esp)
     80486e6:	e8 d5 fe ff ff       	call   80485c0 <std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, int)@plt>
     80486eb:	c9                   	leave  
     80486ec:	c3                   	ret    
     80486ed:	8d 76 00             	lea    0x0(%esi),%esi
    

    Also cmp mit jbe, also Macro-fusion 🙂



  • abc.w schrieb:

    ...

    Danke für die feine Erklärung.


Anmelden zum Antworten