Tangent Funktion in der FPU?



  • Hi,

    ich versuche mir gerade etwas inline assembler beizubringen und als beginn habe ich mir die FPU vorgenommen. Damit konnte ich schon paar feine dinge machen z.B. eine Absolut funktion, eine sinus, cosinus und wurzelfunktion *stolz ist*

    Aber nun möchte ich eine tan-Funktion machen, doch wie mach ich das?

    Ich könnte die das sinusergebnis durch das cosinus ergebnis teilen um tan zu bekommen, aber das wäre nicht grade sehr schnell? gibt es dafür irgendein ftan?

    So habe ich meine sinus-Funktion gestaltet, hat jemand einen verbesserungsvorschlag?

    template<typename T> inline const T sin (const T& value) 
    { return static_cast<T>(0); }
    
    template<> inline const float sin (const float& value)
    {
        static float result = 0.0f;
        result = value;
    
        __asm { finit           }; // FPU initialisieren
        __asm { fld     result  }; // Wert in das FPU Register schieben
        __asm { fsin            }; // FPU-Sinus durchführen
        __asm { fst     result  }; // Wert zurück in 'result' schieben
    
            // Fertig!
        return (result);
    }
    

    Danke im voraus!


  • Mod

    FPU beginner schrieb:

    Hi,

    ich versuche mir gerade etwas inline assembler beizubringen und als beginn habe ich mir die FPU vorgenommen. Damit konnte ich schon paar feine dinge machen z.B. eine Absolut funktion, eine sinus, cosinus und wurzelfunktion *stolz ist*

    Aber nun möchte ich eine tan-Funktion machen, doch wie mach ich das?

    Ich könnte die das sinusergebnis durch das cosinus ergebnis teilen um tan zu bekommen, aber das wäre nicht grade sehr schnell? gibt es dafür irgendein ftan?

    So habe ich meine sinus-Funktion gestaltet, hat jemand einen verbesserungsvorschlag?

    verschiedene. zunächst einmal ist sin/cos noch eine grössenordnung langsamer als eine division, insofern spielt diese nur eine untergeordnete rolle. im übrigen kann man auch mit veringerter genauigkeit arbeiten um einen geschwindigkeitsvorteil zu erreichen. das setzen einer geringeren genauigkeit muss dafür aber global sein, da das verändern des control-worts sehr, sehr langsam ist (wenn mehr als 2 verschiedene werte im spiel sind - grössenordnung ist ~150 takte beim P4). das bedeutet nat. auch, das die verwendung von finit so ungefähr das schlimmste ist, was man machen kann. im übrigen ist die verwendung statischer variablen hier nicht sinnvoll und nat. auch nicht thread-safe - stattdessen sollte die variable gleich by value übergeben werden.

    template<typename T> inline const T tan(T value) 
    { return static_cast<T>(0); }
    
    template<> inline const float tan(float value)
    {
        __asm fld	value
        __asm fsincos
        __asm fdivp st(1),st(0)
    }
    

    wenn es um geschwindigkeit geht, bringt derartige microoptimierung allerdings gar nichts - compiler intrinsics sind hier wesentlich effitienter.



  • compiler intrinsics sind hier wesentlich effitienter.

    Erklär das bitte mal etwas genauer, weniger als 3 Assemblerstatements geht wohl nicht oder



  • Huhu? Ich warte auf Informationen!


  • Mod

    das problem besteht prinzipiell in den genutzten resourcen - die registerbenutzung ist fest, und kann daher durch den compiler nicht angepasst werden. es wäre z.b. möglich, dass das argument bereits in einem register vorhanden ist, dann muss es erst unnötig dupliziert werden. es wäre auch denkbar, dass eine sse basierte lösung effizienter ist - wenn andere teile der berechnung ebenfalls damit ausgeführt werden. und nicht zuletzt verhindert inline assembler häufig die eliminierung gemeinsamer sub-ausdrücke, so dass komplexere ausdrücke dann i.d.R. nicht optimal aufgelöst werden.

    ganz nebenbei: man kann die obige funktion erheblich beschleunigen, wenn man die rechengenauigkeit veringert.



  • Wieso sollte man das beschleunigen können, werden nicht in der fpu alle Werte in double gerechnet, das "casten" in den gewünschten Typ (double,float) findet erst beim auslesen des Werts statt.

    Die Frage ob man es besser machen kann als eine Compiler der optimiert, und der dann solche Effekte wie Wert schon vorhanden ausnutzt ist hier glaube nicht die Frage.

    Die Frage war eher ob es einen effektivern Weg eine Funktion in der FPU zu bedienen.

    Ich kann mir nicht vorstellen, das ein optimierender Compiler in der Lage ist, den kompleten Algorithmus für einen Punkt des "st.." normalen klassischen Apfelmännchens so zu optimieren, daß die Iteration komplett in der FPU-abläuft bis auf die Überprüfung ob der Grenzwert zur Divergenz überschritten ist.


  • Mod

    Wieso sollte man das beschleunigen können, werden nicht in der fpu alle Werte in double gerechnet, das "casten" in den gewünschten Typ (double,float) findet erst beim auslesen des Werts statt.

    tatsächlich benutzt die fpu weder float noch double sondern eine speziellen 80bit typ. das hat unter anderem zufolge, dass der compiler relativ viele operationen mit speicheroperanden durchführt um IEEE-konforme rundung zu gewährleisten, es sei denn fastmath ist aktiviert. aber das nur nebenbei. die interne darstellung hat zwar 64 signifikante bits, dennoch kann man die genauigkeit von operantionen durch geeignetes setzen des controlwortes beeinflussen (hierfür ist auch kein inline assembly nötig). die genauigkeit des ergebnisses ist dann eingeschränkt, dafür sind insbesondere komplexe operationen erheblich schneller, beim P4 je nach operation um ca. 50-100%. und wenn man ohnehin nur floats benutzt, bringt eine höhere interne genauigkeit am ende ja nichts.

    Die Frage ob man es besser machen kann als eine Compiler der optimiert, und der dann solche Effekte wie Wert schon vorhanden ausnutzt ist hier glaube nicht die Frage.

    ich denke das ist schon eine frage, wenn derartige microoptimierung dem compiler dann am ende im weg ist.

    Die Frage war eher ob es einen effektivern Weg eine Funktion in der FPU zu bedienen.

    eine funktion sicher nicht, aber in jeder reellen anwendung benutzt man komplexere operationen, die auf diesen einfacheren aufbauen. und wenn dann inline assembly den compiler behindert, geht jeder vorteil durch den overhead zwischen funktionsaufrufen wieder verloren.

    Ich kann mir nicht vorstellen, das ein optimierender Compiler in der Lage ist, den kompleten Algorithmus für einen Punkt des "st.." normalen klassischen Apfelmännchens so zu optimieren, daß die Iteration komplett in der FPU-abläuft bis auf die Überprüfung ob der Grenzwert zur Divergenz überschritten ist.

    vielleicht nicht, in jedem falle aber gelingt das eben auch nicht mit solchen minimalfunktionen wie oben (gut möglich, dass so ein algorithmus gar nicht optimal ist, und der compiler ganz absichtlich auch speicheroperanden benutzt - das zeitverhalten moderner prozessoren ist sehr komplex). hier muss man eben den algorithmus als ganzes betrachten und ihn nicht zu sehr zerlegen.

    p.s. obige ausführungen gelten so nur für vc++ ... ich bin ein grosser fan von gcc inline assembly 🙂



  • Da er das ganze macht um sich mit dem inline Assembler vertraut zu werden, ist sein Weg glaube nicht so verkehrt mal sehen wie er sich weiterentwickelt.

    Ist es eigentlich sinnvoll wenn man nur float (32Bit IEEE) rechnen möchte auf die SSE auszuweichen?

    Das double bezog dich auch nur auf die erreichte IEEE Rechengenauigkeit.



  • PAD schrieb:

    Das double bezog dich auch nur auf die erreichte IEEE Rechengenauigkeit.

    Ja, aber die FPU rechnet nicht nur auf 53 Bits (IEEE) genau, sondern auf 64.

    Ist es eigentlich sinnvoll wenn man nur float (32Bit IEEE) rechnen möchte auf die SSE auszuweichen?

    Wenn du davon ausgehen kannst, dass dein Programm nur auf CPUs mit SSE laufen wird, auf jeden Fall. Vor allem am P4 ist das viel schneller.



  • @Ringding Das mag schon sein aber der Datentyp in den das ganze in c / c++ gespeichert wird ist double. Zumindest die M$ Produkte kennen meines Wissens keine 80 Bit Variablen, im Gegensatz zum Borland C/C++ Compiler 3.1 unter Dos der kannte long double mit diesem 80 Bit fornmt. Wie es mit anderen aktuellen Compilern aussieht weis ich nicht.

    Und in IEEE ist meines Wissens auch nur 32 und 64 Bit float definiert.

    Eigentlich ist es achade das Intel von der Differenz von 16 Bit nur 11 Bit in die Mantisse gesteckt hat, beim Exponenten hätten sie es sich sparen können.


  • Mod

    genaugenommen sind es nur 10 bit mehr. im extended format wird die führende 1 der mantisse immer mitgespeichert, wärend sie bei den 32- und 64-bit formaten unterdrückt wird, es sei denn, es liegt ein underflow vor.


Anmelden zum Antworten