float vs double



  • Mit

        stopwatch timer( resolution::ms );
        const int loops = 1000000;
    
        double d{};
        std::cout << "\ndouble: ";
        timer.start();
        for ( int i = 0; i < loops; ++i )
        {
            d += doubleTest();
        }
        timer.stop();
        std::cout << "\nloops: " << loops << "  time: " << timer.elapsed() << " ms\n";
    
        float f{};
        std::cout << "\nfloat: ";
        timer.start();
        for ( int i = 0; i < loops; ++i )
        {
            f += floatTest();
        }
        timer.stop();
        std::cout << "\nloops: " << loops << "  time: " << timer.elapsed() << " ms\n";
    

    komme ich auch wieder auf

    double:
    loops: 1000000  time: 63.3185 ms
    
    float:
    loops: 1000000  time: 29.4841 ms
    

    Sollte ich wohl ein "lebensnäheres" Beispiel nehmen?



  • @swordfish sagte in float vs double:

    Einen Funktionsaufruf oder Addition, Subtraktion, Multiplikation oder Division wirst Du wohl erkennen?

    Eigentlich, keine Ahnung. Müsste ich mal Assemblercode sehen, ob ich entsprechendes erkennen kann. Hab zwar schon öfter welchen gesehen, aber man müsste mir schon sagen, was dort was sein soll.
    Hast ja ein Beispiel gebracht. Kann ich ja mal nachschauen.



  • @lemon03 sagte in float vs double:

    komme ich auch wieder auf

    f bzw. d wird ja nach der Schleife wieder nicht verwendet. Ich als Compiler würd mir also denken: "Aufsummieren?? Wozu?? Ich bin doch nicht sein Trottel ... braucht das Ergebnis sowieso nicht!".

    Aber alles was wir hier diskutieren ist graue Theorie. Welcher Compiler? Welche Platform?



  • Compiler ist gcc. Plattform ist win10x64. Gemeint?

    Bei meiner Suche wurde schon erwähnt, das es auch einen Unterschied gibt zwischen x86 und x64. Dann aber wieder nicht. Der Schluss ist immer "hängt davon ab...".

    Der Ordner in meinem MinGW lautet x86_64-w64-mingw32.



  • PS:

    @swordfish sagte in float vs double:

    f bzw. d wird ja nach der Schleife wieder nicht verwendet.

    Ups, habe ich verpennt... Jetzt erst gesehen.



  • Hier noch einmal ein vollständiger Test mit std::chrono:

    #include <iostream>
    #include <cmath>
    #include <chrono>
    
    double doubleTest()
    {
        double a = 1000, b = 45, c = 12000, d = 2, e = 7, f = 1024;
        double a_ = std::sin( a );
        double b_ = std::asin( b );
        double c_ = std::sqrt( c );
        double d_ = d + d - d + d;
        double e_ = e * e + e * e;
        double f_ = f / f / f / f / f;
    
        return a_ / b_ / c_ / d_ / e_ / f_;
    }
    
    float floatTest()
    {
        float a = 1000, b = 45, c = 12000, d = 2, e = 7, f = 1024;
        float a_ = std::sin( a );
        float b_ = std::asin( b );
        float c_ = std::sqrt( c );
        float d_ = d + d - d + d;
        float e_ = e * e + e * e;
        float f_ = f / f / f / f / f;
    
        return a_ / b_ / c_ / d_ / e_ / f_;
    }
    
    int main()
    {
        const int loops = 1000000;
    
        std::cout << "double: ";
        double d = 0;
        auto double_begin = std::chrono::high_resolution_clock::now();
        for ( int i = 0; i < loops; ++i )
        {
            d += doubleTest();
        }
        auto double_end = std::chrono::high_resolution_clock::now();
        std::cout << "\nloops: " << loops << "  time: " << std::chrono::duration_cast<std::chrono::microseconds>(double_end - double_begin).count() << " microseconds\nresult: " << d << std::endl;
    
        std::cout << "\nfloat: ";
        float f = 0;
        auto float_begin = std::chrono::high_resolution_clock::now();
        for ( int i = 0; i < loops; ++i )
        {
            f += floatTest();
        }
        auto float_end = std::chrono::high_resolution_clock::now();
        std::cout << "\nloops: " << loops << "  time: " << std::chrono::duration_cast<std::chrono::microseconds>(float_end - float_begin).count() << " microseconds\nresult: " << f << std::endl;
    
        return 0;
    }
    

    Allerdings variiert das Ergebnis auch sehr stark, was wohl daran liegen wird, dass das Betriebssystem nun einmal nicht immer alle Berechnungen am Stück erledigt.
    Nach einigen Versuchen:
    double: 6300µs-15400µs
    float: 7000µs-14900µs

    Ich würde hier also keine generelle Aussage treffen und von eigentlich keinem Unterschied sprechen.



  • @lemon03 sagte in float vs double:

    Gibts da ein Tool vor

    Ja, dein Compiler ist das Tool. Da gibts normalerweise eine Option, welche den ASM ausgibt.

    Ich wuerde erstmal erwarten das es immer etwa doppelt so schnell ist, weil es mit floats nur die halbe Speicherbandbreite braucht.



  • @unterfliege sagte in float vs double:
    ....

    Ich würde hier also keine generelle Aussage treffen und von eigentlich keinem Unterschied sprechen.

    Weil seit dem Intel Pentium Prozessor Fliesskommazahlen vom integrierten Fliesskommaprozessor in Hardware berechnet werden und den wird inzwischen jeder moderne Compiler benutzen.



  • Was würde man unter einem nicht-modernen Compiler verstehen?



  • Borland C++ 3.1 zB.



  • @swordfish sagte in float vs double:

    Borland C++ 3.1

    Aha. 1992? 😃

    Und man kann davon ausgehen, das solche Compiler noch regelmäßig genutzt werden? Weil sonst, verzeiht mir die Feststellung, alle bisherigen Antworten bis der von Burkhi obsolet wären?

    Ist float vs double also nur eine Stil-Frage?



  • @lemon03 sagte in float vs double:

    Ist float vs double also nur eine Stil-Frage?

    OMFG. Miss es!



  • Oh my fucking God?

    Ich werde das halt mit meinen eigenen Routinen testen, und wenn sich irgendein Unterschied auftut, werde ich mich entsprechend entscheiden. Und damit wird dieses Thema für mich ausreichend behandelt sein. Danke.



  • @lemon03 sagte in float vs double:

    Ich werde das halt mit meinen eigenen Routinen testen, und wenn sich irgendein Unterschied auftut, werde ich mich entsprechend entscheiden.

    👍



  • BTW: Man muss die Werte nicht unbedingt ausgeben. Wenn die Variable wo man das Ergebnis reinschiebt volatile ist reicht das. Schreiben von volatile Zeugs gilt als beobachtbar, auch wenn es in Wirklichkeit gar nicht beobachtbar ist. Also z.b. auch wenn die Variable lokal ist und der Scope der Variable direkt nach der Zuweisung endet. Compiler halten sich trotzdem daran.



  • @lemon03 sagte in float vs double:

    @swordfish sagte in float vs double:

    Borland C++ 3.1
    

    Aha. 1992? 😃

    Und man kann davon ausgehen, das solche Compiler noch regelmäßig genutzt werden? Weil sonst, verzeiht mir die Feststellung, alle bisherigen Antworten bis der von Burkhi obsolet wären?

    Ist float vs double also nur eine Stil-Frage?

    Eher wohl eine Frage der benötigten Genauigkeit.😉



  • Was die Performance angeht... das kann je nach CPU total unterschiedlich gehen. Folgende Faktoren spielen da mit

    • Wie @TGGC schon geschrieben hat die Speicherbandbreite - wenn das der limitierende Faktor ist, ist klar dass man mit float schneller ist als mit double weil float nur 1/2 so viel Speicher braucht.
    • Die Vektorregister und der Befehlssatz. Oft werden die selben Vektorregister für single- und double-precision verwendet, wobei man mit single-precision nur 1/2 so viele Werte in ein Register reinbringt wie mit double. Also z.B. ein Register mit entweder 8x float oder 4x double. Die Befehle arbeiten dann immer mit nem ganzen Register, d.h. man bekommt dann pro Befehl entweder 8 oder halt nur 4 Operationen. Wenn genügend Rechenwerke zu Verfügung stehen die die jeweilige Operation beherrschen dann kann das der limitierende Faktor sein.
    • Die Rechenwerke der CPU. CPUs haben typischerweise mehrere Rechenwerke die jeweils mehrere Operationen beherrschen und entweder single oder double-precision sein können. Wobei die double Rechenwerke typischerweise auch für single-precision verwendet werden können. Wenn die CPU jetzt z.B. nur zwei double-precision Rechenwerke hat die dividieren können aber zusätzlich noch zwei single-precision Rechenwerke die dividieren können, und man viel dividiert, dann ist auch klar dass man mit single-precision mehr Performance bekommen wird.
    • Je nach Programm kann die Latenz eines Befehls mehr oder weniger wichtig sein. Also die Zeit die benötigt wird bis das Ergebnis feststeht. Wenn man Rechenoperationen hat wo immer das Ergebnis der einen Operation ein Input für die nächste ist, dann spielt die Latenz eine grössere Rolle als wenn die Berechnungen unabhängig sind. (In mehreren Rechenwerken können ja mehrere unabhängige Berechnungen nebeneinander her laufen.) Und bei manchen Operationen ist die unterschiedlich für single- und double-precision. Speziell die mittel aufwendigen Operationen wie z.B. multiplizieren oder dividieren können da betroffen sein.

    Und dann spielt natürlich mit rein wie das Programm aussieht, welchen Compiler man verwendet, ob man in mehreren Threads parallel rechnet oder nicht usw.

    Hier https://en.wikipedia.org/wiki/FLOPS gibt's ne schöne Tabelle aus der man entnehmen kann dass es kaum CPUs gibt bei denen single- oder double-precision keinen Unterschied macht. Meist ist der maximale Durchsatz bei double-precision 1/2 vom maximalen Durchsatz bei single-precision -- oft auch noch weniger.


Anmelden zum Antworten