Aufruf von int main(){return 0;} mit gcc 4.1.1



  • IIRC ist mov eax,0x0 schon seit Pentium-Zeiten schneller als xor eax,eax. Das Gerücht hält sich halt hartnäckig.



  • ad 1: das ist ja ein echt interessanter Paradigmenwechsel. NobuoT ist aber vor einem jahr noch eindeutig für xor eax,eax eingetreten
    ad 2: kann das jemand erklären?



  • SG1 schrieb:

    IIRC ist mov eax, 0x0 schon seit Pentium-Zeiten schneller als xor eax, eax. Das Gerücht hält sich halt hartnäckig.

    Das kommt drauf an. Ich bin der Meinung, man muss einfach selber Takte messen. Siehe u.a. z.B. hier: http://www.c-plusplus.net/forum/viewtopic-var-p-is-1691990.html

    Erhard Henkes schrieb:

    1. wieso mov eax,0x0 und nicht das viel beschworene xor eax,eax

    Das ist wegen der eingestellten Compiler-Optimierungsstufe. Es gibt -Os, -O, -O1, -O2, -O3, dann noch -march usw...

    Erhard Henkes schrieb:

    1. wieso wird da ein call b <_main+0xb> eingefügt? Warum diese return address auf dem stack?

    Wegen call b <_main + 0xb>: Man könnte das Programm statisch (-static) linken, disassemblieren und schauen, was da passiert. Welche "return address" bezieht sich die Frage 😕



  • Erhard Henkes schrieb:

    ad 1: das ist ja ein echt interessanter Paradigmenwechsel. NobuoT ist aber vor einem jahr noch eindeutig für xor eax,eax eingetreten

    Ka. Mag sein. Fuer den Hausgebrauch halte ich xor auch nach wie vor fuer die elegantere Loesung. Auf der anderen Seite habe ich es aber schon ein ganzes Weilchen aufgegeben, die letzten Geheimnisse der/des xten x86-Frickel-An-/Umbaus ergruenden zu wollen. Bei den ganzen Implementierungen grenzt das fuer ein allgemein x86-kompatibles Programm bald an reine Kaffesatzleserei.
    Wird wohl was mit den Optimierungseinstellungen zu tun haben, wie abc.w schon schrieb.



  • ad 1)
    Ich habe bei code::blocks mal nachgeschaut: es ist standardmäßig keine Optimierung eingeschaltet.

    Tests ergaben folgendes:

    No speed optimization, speed optimization -O, -O1 :

    00000000 <_main>:
    0: 55 push ebp
    1: 89 e5 mov ebp,esp
    3: 83 e4 f0 and esp,0xfffffff0
    6: e8 00 00 00 00 call b <_main+0xb>
    b: b8 00 00 00 00 mov eax,0x0
    10: c9 leave
    11: c3 ret

    Speed optimization -O2, -O3, size optimization -Os:

    00000000 <_main>:
    0: 55 push ebp
    1: 89 e5 mov ebp,esp
    3: 83 e4 f0 and esp,0xfffffff0
    6: e8 00 00 00 00 call b <_main+0xb>
    b: 31 c0 xor eax,eax
    d: c9 leave
    e: c3 ret

    size ist klar, aber auch ab -O2 wird xor eax,eax doch bevorzugt.



  • Früher hatte man xor eax,eax aus Geschwindigkeitsgründen eingesetzt, ist aber mittlerweile egal, mov eax,0 ist heute genauso schnell. Aus Gründen der Leserlichkeit empfiehlt sich hier und da also mov eax,0, aber für Eingeweihte hat der Befehl xor eax,eax fast schon eigensymbolische Bedeutung, für wen ist also was lesbarer bzw. gängiger?
    Agner Fog ( http://www.agner.org/ ) beschreibt in seinen stets aktuell gehaltenen manuals aber auch, dass der xor Befehl gut ist, um (Register) Abhängigkeiten aufzubrechen.

    der Call Befehl(hat ja im moment noch gar keine Adresse, ruft also nur den nächsten Befehl auf) und der ret, wie auch der leave Befehl haben mit der Aufrufprozedur zu tun, die erschließt sich aber, glaube ich, auch für Mitleser eher mit oder nach etwas mehr Zusammenhangsinformationen. Also, wie sieht denn das Ganze aus, wenn du eine einfache kleine Assembler Rechnerei von C aus aufrufst, und per printbefehl o.ä. ausgibst?



  • Mich wunderte eigentlich vor allem das unter Linux (Ubuntu 10.10 mit gcc 4.4.5) so ziemlich genau das heraus kommt was man erwartet:

    8048394:	55                   	push   ebp
    8048395:	89 e5                	mov    ebp,esp
    8048397:	b8 00 00 00 00       	mov    eax,0x0
    804839c:	5d                   	pop    ebp
    804839d:	c3                   	ret
    

    Also das sichern des Frame Pointers am Anfang und das Wiederherstellen am Ende. Mit Optimierungen (-O3 und -march=native beim intel core2 duo) wird das mov eax, 0x0 zum xor eax, eax, und das pop ebp zum leave. Aber kein Anzeichen vom dem and das für irgendein Alignment sorgt und dem Call das nur etwas auf dem Stack legt.



  • nachtfeuer schrieb:

    Aus Gründen der Leserlichkeit empfiehlt sich hier und da also mov eax,0

    UNFUG



  • volkard schrieb:

    nachtfeuer schrieb:

    Aus Gründen der Leserlichkeit empfiehlt sich hier und da also mov eax,0

    UNFUG

    hast du an der stelle aufgehört zu lesen? Satzteile zu zitieren ist irgendwie nicht die feine art, da reißt man leicht was aus dem kontext.



  • Jester schrieb:

    hast du an der stelle aufgehört zu lesen?

    Ja, selbstverständlich.



  • Irgendwo verstaendlich. Die Auswahl von in mehrfacher Hinsicht verschiedenen Codes unter dem Aspekt der Lesbarkeit ist fuer Assembler, vor allem im Kontext von Effizienz, einfach indiskutabel.



  • Nobuo T schrieb:

    Assembler, vor allem im Kontext von Effizienz, einfach indiskutabel.

    UNFUG, Assembler ist im Kontext von Effizienz doch gerade wichtig! (super strategie, halbe sätze zu zitieren, oder? :))



  • Ist vielleicht auch nicht ganz irrelevant: xorl %eax,%eax braucht nur zwei Bytes. movl $0x0,%eax braucht 5. Die Freude von variablem Befehlsencoding :). (Naja, die wahre Freude sind natürlich "Sprünge in Befehle" ;))

    edit: http://www.intel.com/Assets/PDF/manual/248966.pdf Section 3.5.1.6



  • Ich habe zu dem Thema ein Tutorial gestartet, das ich vor die OSDev-Programmierung vorschalten möchte. Bitte um konstruktive Kritik:
    http://www.henkessoft.de/C/C-Programming Under The Hood.htm
    (ich habe es diesmal gleich in englisch geschrieben)



  • rüdiger schrieb:

    Ist vielleicht auch nicht ganz irrelevant: xorl %eax,%eax braucht nur zwei Bytes. movl $0x0,%eax braucht 5.

    stimmt, und der Spareffekt bei 64bit ist noch größer, also
    B8 0000 0000 0000 0000
    vs
    31C0
    oder
    33C0

    und bei xmm registern gehts glaube ich fast schon gar nicht anders. 😉

    Nobuo T schrieb:

    Irgendwo verstaendlich. Die Auswahl von in mehrfacher Hinsicht verschiedenen Codes unter dem Aspekt der Lesbarkeit ist fuer Assembler, vor allem im Kontext von Effizienz, einfach indiskutabel.

    nirgendwo verständlich. Aber Mist...vielleicht sollte ich doch mal über effektiveres posting nachdenken...;)

    Für mich hat gutes, "effizentes Programmieren" ganz normal mit Lesbarkeit und Pflegbarkeit zu tun. Komischerweise ist "effizienter" Code oft lesbarer und einfacher zu handhaben als ineffizenter Code.
    Man muss abwägen können.
    Vielleicht ist die Assemblersprache trotz der so wirklich leistungsfähigen Assembler, die man heute kostenlos im Internet angeboten bekommt, aus dem Blickfeld geraten, weil der Irrglaube besteht, schneller asmcode = unleserlich, ähnlich wie man meinen könnte, gute medizin muss bitter sein oder das zur PC- Betriebssystemprogrammierung ein C/C++-Compiler praktischer ist als ein aktueller leistungsfähiger Assembler. Hängt davon ab...



  • C/C++? Kenn ich net.



  • nachtfeuer schrieb:

    Für mich hat gutes, "effizentes Programmieren" ganz normal mit Lesbarkeit und Pflegbarkeit zu tun. Komischerweise ist "effizienter" Code oft lesbarer und einfacher zu handhaben als ineffizenter Code.
    Man muss abwägen können.

    Falls du ernsthaft meinst, Lesbarkeit und Pflegbarkeit von Assemblercodes ueber die Auswahl der OpCodes herstellen zu wollen, musst du irgendwas beim Programmieren in Assembler missverstanden haben. Assembler programmiert man doch ueberhaupt erst, um fuer spezielle Anwendungen moeglichst optimierten, nicht um besonders lesbaren Code zu erhalten. Zur Erhoehung der Uebersichtlichkeit kann man dann assemblerabhaengige strukturierende Meta-Codes, Macros und vor allem Kommentare benutzen.
    Fuer von sich aus besonders gut lesbaren, aesthetischen Code bedient man sich einer abstrakten Hochsprache mit gut optimierendem Compiler, bei der die Auswahl und Anordnung der Befehle im Zweifelsfall weniger kritisch ist.



  • Ich denke, vom optimierten Code sollte man sprechen, wenn man eine Version A und eine Version B zur Hand hat, die man miteinander vergleichen kann. Dann kann man sich austoben und behaupten, A wäre schneller als B oder B ist kleiner als A oder was auch immer.
    Speziell bei "xor reg, reg" vs. "mov $0, %eax" (man beachte, kein movl, ein mov reicht in diesem Fall aus): Ich konnte keine Unterschiede in der Ausführungszeit feststellen, beide sind genauso schnell, die xor-Variante ist kleiner, aber das wäre ja nur für "optimize for size" wichtig. Ich bin aber natürlich keine Autorität auf diesem Gebiet, so dass jeder meine Aussage ruhig ignorieren kann.
    Wegen:

    rüdiger schrieb:

    http://www.intel.com/Assets/PDF/manual/248966.pdf Section 3.5.1.6

    alles schön und gut, aber es wird nur kurz angedeutet, dass der xor-Befehl Flags-Register verändert und das auch noch partiell und das Beispiel mit der for-Schleife verwendet ein signed int als Zählvariable (keine Macro Fusion in diesem Fall, warum nicht 😕 ) - alles Kleinigkeiten, aber andererseits nennt sich das Dokument als "Optimization Manual".
    Interessant ist aber noch folgendes:

    The Pentium 4 processor provides special support for XOR, SUB, and PXOR operations when executed within the same register. This recognizes that clearing a register does not depend on the old value of the register. ...

    Man hat da in der CPU tatsächlich eine "black box" eingebaut, die aufpasst, dass wenn jemand "xor reg, reg" macht, ein Register schnell auf 0 zurücksetzt, damit es ja nicht langsamer als ein "mov $0, %eax" ist 😕



  • Bitte um konstruktive Kritik:
    http://www.henkessoft.de/C/C-Programming%20Under%20The%20Hood.htm

    Offensichtlich verfolgt der GCC 4.1.1 ein neues (an "x64" angelehntes) Paradigma:

    00401349    mov    DWORD PTR [esp+0x8],0xfffffff2    
    00401351    mov    DWORD PTR [esp+0x4],0xfffffff4
    00401359    mov    DWORD PTR [esp],0x2a
    00401360    call   0x401318 <doSomething>
    

    Argumente werden nicht (wie üblich) via PUSH auf den Stack "gepushed", sondern direkt in den Stack geschrieben. Da wäre es m.E. sinnvoller, den "Verbleib" der Argumente im Stack des Callee nicht nur via [EPB + X], sondern auch via [ESP + X] aufzuzeigen. Beinahe hätte ich noch vermutet, daß der Callee den EBP-basierten Prolog/Epilog nicht mehr hat. Hmmm, aber wie man sich doch täuschen kann.



  • merker schrieb:

    ... Beinahe hätte ich noch vermutet, daß der Callee den EBP-basierten Prolog/Epilog nicht mehr hat. Hmmm, aber wie man sich doch täuschen kann.

    Wenn ich richtig verstanden habe, was Du meinst, müsste es mit dem gcc Compiler Parameter -fomit-frame-pointer einstellbar sein...


Anmelden zum Antworten