Schnelligkeit von C-Code



  • abhängig vom compiler und vom programm selbst werden bei aufrufen von unterprogrammen auch gerne mal die daten auf den stack geschoben und danach wieder heraufgeholt, was auch wieder zeit frisst.

    inline-assembler bringt auch sehr oft zeitvorteile, genauso wie die verwendung des schlüsselworts register.



  • abhängig vom compiler und vom programm selbst werden bei aufrufen von unterprogrammen auch gerne mal die daten auf den stack geschoben und danach wieder heraufgeholt, was auch wieder zeit frisst.

    inline-assembler bringt auch sehr oft zeitvorteile, genauso wie die verwendung des schlüsselworts register.

    Ist das ein Troll-Versuch - oder ernst gemeint?



  • natürlich ist das ernst gemeint.



  • Das register -Schlüsselwort bringt nicht wirklich viel, weil Compiler heutzutage schlau genug sind, zu erkennen, welche Variablen in den Registern bleiben können und welche im Hauptspeicher bleiben müssen. Ich habe vereinzelt Variablen explizit register zuweisen müssen, aber dann ging es meist um Emulations- oder anderen hochoptimierten Code.

    Als Anfänger packt man dann überall register rein, denkt sich, man ist voll schlau, aber es bringt nicht wirklich was.

    Außerdem hat der TE eine RISC-Architektur. Die zeichnen sich in der Regel durch viele, viele Register aus, und das weiß der Compiler auch.



  • und darauf kann man sich verlassen?

    was ist mit inline-assembler? ich habe dazu gelernt, dass assembler gewissermaßen direkt auf die funktionen der cpu zugreift bzw. unmittelbar in opcodes umgewandelt werden kann und daher immer schneller ist, als der compiler es hinbekommt, wenn man jetzt so späße wie mehrfache addition statt multiplikation und derartiges vermeidet.


  • Mod

    HansKlaus schrieb:

    und darauf kann man sich verlassen?

    was ist mit inline-assembler? ich habe dazu gelernt, dass assembler gewissermaßen direkt auf die funktionen der cpu zugreift bzw. unmittelbar in opcodes umgewandelt werden kann und daher immer schneller ist, als der compiler es hinbekommt, wenn man jetzt so späße wie mehrfache addition statt multiplikation und derartiges vermeidet.

    Häh? Was meinst du denn, was der Compiler erzeugt, wenn nicht das? C ist nicht Java, da gibt es keine Zwischenschritte zwischen Programm und CPU. Und da der Compiler mit seinen tausenden Optimierungstricks in aller Regel viel besseren Maschinencode programmieren kann als der erfahrendste Assemblerhacker, erzeugt er in aller Regel auch viel schnelleren Code. Die einzige nennenswerte Ausnahme sind dabei allerneueste CPU-Features, die älteren Compilern einfach noch nicht gut bekannt sind.



  • naja ich frag deshalb so bescheuert, weil wir als laboraufgabe jeweils mit outp aus der conio.h und mit inline-assembler und out ein rechtecksignal erzeugen und dann erklären sollten, wieso das signal mit c irgendwo bei 150kHz und mit inline-assembler bei 270 kHz liegt und die botschaft war eben, dass der compiler das nicht so vernünftig hinbekommt und dass zeitkritische funktionen dann in (inline-) assembler zu realisieren sind.



  • HansKlaus schrieb:

    naja ich frag deshalb so bescheuert, weil wir als laboraufgabe jeweils mit outp aus der conio.h und mit inline-assembler und out ein rechtecksignal erzeugen und dann erklären sollten, wieso das signal mit c irgendwo bei 150kHz und mit inline-assembler bei 270 kHz liegt und die botschaft war eben, dass der compiler das nicht so vernünftig hinbekommt und dass zeitkritische funktionen dann in (inline-) assembler zu realisieren sind.

    So ein Problem hatte ich vor 20 Jahren auch mal.
    Allerdings war der C-Compiler so gut, dass ich es nicht (deutlich genug) schneller bekommen habe.
    Allerdings war das auch auf einem Atari ST.



  • HansKlaus schrieb:

    und darauf kann man sich verlassen?

    Nein, kann man nicht. Deswegen wird dir auch jeder, den du bezüglich deines Algorithmus fragst, zumindest nahelegen, selbst zu testen, was schneller ist. Wenn es um zeitkritische Aufgaben geht, ist der Messaufwand gerechtfertigt.

    HansKlaus schrieb:

    naja ich frag deshalb so bescheuert, weil wir als laboraufgabe jeweils mit outp aus der conio.h und mit inline-assembler und out ein rechtecksignal erzeugen und dann erklären sollten, wieso das signal mit c irgendwo bei 150kHz und mit inline-assembler bei 270 kHz liegt

    Und hast du dir den generierten Maschinencode angeschaut und festgestellt, warum der Compiler so langsamen Code generiert? Es gibt auch das Phänomen, dass Compiler halt nicht schlau genug sind, um zu optimieren.

    Wobei ich erst einmal frech behaupten würde, dass die meiste Zeit eh in outp verbracht wird.

    Mich würde der C-Code, der erzeugte Maschinencode, und das schnellere Inline-Assembly interessieren, damit ich einen direkten Vergleich habe und nicht raten müsste, warum das Assembly schneller ist.



  • Auf jeden Fall sollte man sich ruhig mal anschauen, was der C Compiler an Assemblercode generiert. Zu 486er Zeiten gab es da tatsächlich noch Unterschiede zwischen

    int a=4,b;
    b = a / 2;
    

    und

    int a=4,b;
    b = a >> 1;
    

    Das bekommen heutige Compiler aber problemlos hin optimiert. 🙂


  • Mod

    Burkhi schrieb:

    Auf jeden Fall sollte man sich ruhig mal anschauen, was der C Compiler an Assemblercode generiert. Zu 486er Zeiten gab es da tatsächlich noch Unterschiede zwischen

    int a=4,b;
    b = a / 2;
    

    und

    int a=4,b;
    b = a >> 1;
    

    Das bekommen heutige Compiler aber problemlos hin optimiert. 🙂

    sogar für 486er.



  • also das programm lautete einmal

    int main()
    {
    //Hier noch bisschen Initialisierung
    
    for(;;)
    {
    outp(PORT,0x01);
    outp(PORT,0x00);
    }
    }
    

    und einmal

    int main()
    {
    //Initialisierung
    
    for(;;)
    {
    _asm 
    { 
    mov al,0x01 
    mov dx,PORT 
    out dx,al
    mov al,0x00 
    mov dx,LPT 
    out dx,al 
    } 
    }
    
    }
    

    wurde beides ganz normal mit dem openwatcom-compiler (den ich natürlich gerade nicht auf dem rechner habe) gebaut. letztenendes unterschieden sich die jeweiligen assemblercodes nur darin, dass in dem c-programm jedes mal eine funktion/unterprogramm namens check oder so aufgerufen wurde und man sagte mir, dass der compiler dieses check dazu benutzt, um überhaupt die richtigen einstellungen zu finden.

    ich hab eben noch einmal nachgesehen, die frequenzen waren 117kHz und 320kHz, also fast das dreifache an unterschied. 🙄



  • HansKlaus schrieb:

    letztenendes unterschieden sich die jeweiligen assemblercodes nur darin, dass in dem c-programm jedes mal eine funktion/unterprogramm namens check oder so aufgerufen wurde und man sagte mir, dass der compiler dieses check dazu benutzt, um überhaupt die richtigen einstellungen zu finden.

    Wenn ich das richtig verstehe, war also das Problem, dass ein Funktionsaufruf, der auch außerhalb der Schleife hätte stehen können, nicht wegoptimiert wurde. Der GCC kennt solche Funktionen als pure functions , die also keine Seiteneffekte besitzen und das Ergebnis nur vom Input abhängt - die also "auch mal weniger oft als geschrieben aufgerufen werden können".

    Ob der Watcom das unterstützt, weiß ich nicht. Aber meines Wissens hat auch der Visual Studio Compiler z.B. Probleme damit, strlen -Calls auf konstante Strings einfach mit der String-Länge zu ersetzen.

    Die korrekte Implementierung wäre natürlich gewesen, den Funktionsaufruf außerhalb der Schleife zu haben. 🙄


  • Mod

    HansKlaus schrieb:

    also das programm lautete einmal

    int main()
    {
    //Hier noch bisschen Initialisierung
    
    for(;;)
    {
    outp(PORT,0x01);
    outp(PORT,0x00);
    }
    }
    

    und einmal

    int main()
    {
    //Initialisierung
    
    for(;;)
    {
    _asm 
    { 
    mov al,0x01 
    mov dx,PORT 
    out dx,al
    mov al,0x00 
    mov dx,LPT 
    out dx,al 
    } 
    }
    
    }
    

    wurde beides ganz normal mit dem openwatcom-compiler (den ich natürlich gerade nicht auf dem rechner habe) gebaut. letztenendes unterschieden sich die jeweiligen assemblercodes nur darin, dass in dem c-programm jedes mal eine funktion/unterprogramm namens check oder so aufgerufen wurde und man sagte mir, dass der compiler dieses check dazu benutzt, um überhaupt die richtigen einstellungen zu finden.

    ich hab eben noch einmal nachgesehen, die frequenzen waren 117kHz und 320kHz, also fast das dreifache an unterschied. 🙄

    Gast3 schrieb:

    Ist das ein Troll-Versuch - oder ernst gemeint?



  • dachschaden schrieb:

    Wenn ich das richtig verstehe, war also das Problem, dass ein Funktionsaufruf, der auch außerhalb der Schleife hätte stehen können, nicht wegoptimiert wurde. Der GCC kennt solche Funktionen als pure functions , die also keine Seiteneffekte besitzen und das Ergebnis nur vom Input abhängt - die also "auch mal weniger oft als geschrieben aufgerufen werden können".

    ja der gcc unterstützt aber keine prozessoren mit 16 bit datenbreite (warum auch immer) und der openwatcom ist doch sonst eigentlich ziemlich gut, oder nicht?

    Aber meines Wissens hat auch der Visual Studio Compiler z.B. Probleme damit, strlen -Calls auf konstante Strings einfach mit der String-Länge zu ersetzen.

    mit scheint, dass compiler so allgemein ihre tücken haben können. muss ja irgendwie auch so sein, wenn es keine fehlerfreien programme geben kann. 🙄

    Die korrekte Implementierung wäre natürlich gewesen, den Funktionsaufruf außerhalb der Schleife zu haben. 🙄

    ja aber wer soll das machen? die wenigsten werden die zeit haben, erst einmal den compiler umzuschreiben und das dann auch noch ausgiebig zu testen.

    andererseits wirds in 95% der fälle sowieso egal sein und in den restlichen 5% nimmt man halt einfach assembler. 😃



  • Ist das ein Troll-Versuch - oder ernst gemeint?

    wenn jemand so nebenbei von register und inline-Assembler anfängt dann ist es schon sehr relevant welcher Kompiler und was für ein Problem optimiert wurde - hier ist es ein alter (zugegeben damals sehr guter DOS C Compiler) und ein absolut triviales IO Beispiel - das ist in dem Kontext eines neueren Kompilers und so allgemein als "schneller" formuliert einfach zu plump - daher die Troll Frage - aber jetzt ist ja klar wo das Wissen/Erfahrung herkommt - hier für diesen Fall unrelevant



  • @HansKlaus - welche Version von Watcom C war das - ich würde mal probieren das ganze mit dem Build von https://github.com/open-watcom (Version 2.0 beta Aug 26 2016 05:40:13 (32-bit)) zu kompilieren - dann kann man direkt vergleichen was der Unterschied zwischen C und inline-Assembler ist



  • so gehts:

    von https://github.com/open-watcom

    den nightly build runterladen

    https://github.com/open-watcom/travis-ci-ow-builds/archive/master.zip

    entpackend

    dann das Beispiel von HansKlaus:

    outp_test.c

    #include <conio.h>
    
    #define PORT 123
    
    int main1()
    {
        //Hier noch bisschen Initialisierung
        for(;;)
        {
            outp(PORT,0x01);
            outp(PORT,0x00);
        }
    }
    
    int main2()
    {
        //Initialisierung
        for(;;)
        {
            _asm
            {
                mov al,0x01
                mov dx,PORT
                out dx,al
                mov al,0x00
                mov dx,PORT
                out dx,al
            }
        }
    }
    
    int main()
    {
        main1();
        main2();
        return 0;
    }
    

    und diese Batch-Datei

    outp_test.cmd

    set WATCOM=C:\Users\dl\Downloads\travis-ci-ow-builds-master\travis-ci-ow-builds-master\rel
    set PATH=%WATCOM%\binnt;%PATH%;
    set INCLUDE=%WATCOM%\h
    set EDPATH=%WATCOM%\EDDAT
    set WIPFC=%WATCOM%\WIPFC
    wcl -q -bt=DOS d:\temp\outp_test.c
    

    und des Rätsels Lösung ist - kein inlineing von den outp-Funktionen
    (oder mir fehlen noch Optimierschalter beim kompilieren)

    seg000:0008 main1           proc near               ; CODE XREF: sub_1003B+6p
    seg000:0008                 mov     ax, 4
    seg000:000B                 call    loc_1004F
    seg000:000E                 push    dx
    seg000:000F
    seg000:000F loc_1000F:                              ; CODE XREF: main1+18j
    seg000:000F                 mov     dx, 1
    seg000:0012                 mov     ax, 7Bh ; '{'
    seg000:0015                 call    sub_10081 ; <=======
    seg000:0018                 xor     dx, dx
    seg000:001A                 mov     ax, 7Bh ; '{'
    seg000:001D                 call    sub_10081 ; <=======
    seg000:0020                 jmp     short loc_1000F
    seg000:0020 main1           endp
    seg000:0020
    seg000:0022
    seg000:0022 ; =============== S U B R O U T I N E =======================================
    seg000:0022
    seg000:0022 ; Attributes: noreturn
    seg000:0022
    seg000:0022 main2           proc near               ; CODE XREF: seg000:0044p
    seg000:0022                 mov     ax, 0Ch
    seg000:0025                 call    loc_1004F
    seg000:0028                 push    bx
    seg000:0029                 push    cx
    seg000:002A                 push    dx
    seg000:002B                 push    si
    seg000:002C                 push    di
    seg000:002D
    seg000:002D loc_1002D:                              ; CODE XREF: main2+17j
    seg000:002D                 mov     al, 1
    seg000:002F                 mov     dx, 7Bh ; '{'
    seg000:0032                 out     dx, al
    seg000:0033                 mov     al, 0
    seg000:0035                 mov     dx, 7Bh ; '{'
    seg000:0038                 out     dx, al
    seg000:0039                 jmp     short loc_1002D
    seg000:0039 main2           endp
    


  • und wenn nicht doch noch Optimierschalter fehlen ist der generierte Code in beiden Fällen semi-optimal - warum wird z.B. dx staendig ueberschrieben?



  • Erik12345679 schrieb:

    Meine Frage wäre nun: Bringt es einen Geschwindigkeitsvorteil, wenn ich meine Variablen außerhalb der zeitkritischen Bereiche anlege (z.B. beim Starten des µP)?

    Generell sind Alignment (auch Datentypkontrolle, -repräsentation oder -wechsel), Jumps verhindern und Abhängigkeiten auflösen (in den Asm-Code gucken und/oder in Schleifen) hilfreich.
    Wenn kein netter Algo greifbar ist, ist vielleicht eine nette Hardwarehilfe beim Prozessor nutzbar.
    Beim letzten wäre zu prüfen, was der Compiler optimieren kann (z.B. Konvertierungen, besondere Befehle oder Parallelgedöns) und was nicht.

    Internetseiten zum Thema Optimierung sollten möglichst aktuell sein und auch Prozessor- und Entwicklungssystem bezogen.
    Diese Seite hier ist noch nicht so alt, beschreibt aber schon weitere wichtige Grundregeln und Hilfen:
    http://stackoverflow.com/questions/10800372/c-tips-for-code-optimization-on-arm-devices


Anmelden zum Antworten