float vs double



  • Hallo,

    es gibt zwar viele Treffer zu dem Thema, ob es einen Unterschied gibt und wann man was einsetzen sollte, größtenteils ist die Ansicht aber, hängt davon ab...
    Ich weiß, das double eine größere Genauigkeit hat, würde aber gerne wissen, wenn man diese Genauigkeit nicht braucht, ob dann float besser (schneller) wäre.

    Dazu habe ich ein Beispiel von stackoverflow genommen und versucht zu messen.

    #include <iostream>
    #include <cmath>
    #include "stopwatch.h"
    
    void doubleTest( int loop )
    {
        std::cout << "\ndouble: ";
        for (int i = 0; i < loop; i++)
        {
            double a = 1000, b = 45, c = 12000, d = 2, e = 7, f = 1024;
            a = std::sin( a );
            b = std::asin( b );
            c = std::sqrt( c );
            d = d + d - d + d;
            e = e * e + e * e;
            f = f / f / f / f / f;
        }
    }
    
    void floatTest( int loop )
    {
        std::cout << "\nfloat: ";
        for (int i = 0; i < loop; i++)
        {
            float a = 1000, b = 45, c = 12000, d = 2, e = 7, f = 1024;
            a = std::sin( a );
            b = std::asin( b );
            c = std::sqrt( c );
            d = d + d - d + d;
            e = e * e + e * e;
            f = f / f / f / f / f;
        }
    }
    
    int main()
    {
        stopwatch timer( resolution::ms );
        const int loops = 1000000;
    
        timer.start();
        doubleTest( loops );
        timer.stop();
        std::cout << "\nloops: " << loops << "  time: " << timer.elapsed() << " ms\n";
        
        timer.start();
        floatTest( loops );
        timer.stop();
        std::cout << "\nloops: " << loops << "  time: " << timer.elapsed() << " ms\n";
    }
    

    Dabei kam ich auf folgendes Ergebnis:

    double:
    loops: 1000000  time: 58.2607 ms
    
    float:
    loops: 1000000  time: 27.9604 ms
    

    mit leicht schwankenden Werten, aber immer dem selben Verhältnis. Habe ich nun was falsch gemacht oder ist es "auf meiner Plattform" tatsächlich besser float zu nehmen?



  • Dein Ergebnis sagt gar nichts aus, da die Funktion nichts tut.



  • Aha, habe die Funktionen so aus einem nichtC++ - Beispiel übernommen.

    Ich müsste ein Ergebnis zurück geben lassen?



  • Ja, irgendwie musst du den Compiler zwingen, die Berechnung tatsächlich durchzuführen.

    Interessaterweise (ich habs mal eben in den Godbolt gepackt) wird als einziges (zumindest von aktuellem clang/gcc) der asin(f)-Aufruf nicht wegoptimiert. Du hast also vermutlich loop Mal asin mit loop mal asinf verglichen.

    Wobei: du solltest selbst checken, was dein Compiler generiert hat.

    Edit: icc18 optimiert die Funktion ganz weg (bis auf das cout)



  • @wob sagte in float vs double:

    Wobei: du solltest selbst checken, was dein Compiler generiert hat.

    Das sagt mit jetzt auf die Schnelle leider nichts. Wie mache ich das?

    Habe den Code zu

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

    geändert. Abgesehen von den ungenutzten d und f in der main, wäre dies sinnvoller?
    Und wenn ja, wäre nicht noch sinnvoller, lieber random-Werte in den Funktionen zu nehmen, statt vorgegebene?



  • @lemon03 sagte in float vs double:

    @wob sagte in float vs double:

    Wobei: du solltest selbst checken, was dein Compiler generiert hat.

    Das sagt mit jetzt auf die Schnelle leider nichts. Wie mache ich das?

    Indem Du Dir den Assemblercode anschaust, den Dein Compiler ausspuckt.

    @lemon03 sagte in float vs double:

    for ( int i = 0; i < loops; ++i )
    {
        double d = doubleTest();
    }
    

    und analog auch die Schleife für float kann ein Compiler wieder komplett Wegoptimieren, da sie keine Seiteneffekte hat. Besser:

    double d{};
    for ( int i = 0; i < loops; ++i )
    {
        d += doubleTest();
    }
    std::cout << d << '\n';
    


  • @swordfish sagte in float vs double:

    Indem Du Dir den Assemblercode anschaust, den Dein Compiler ausspuckt.

    Gibts da ein Tool vor, oder kann ich das online machen, wie es manchmal gezeigt wird?
    Und wenn ich jetzt gar keine Ahnung von Assembler habe, schaue ich dann einfach, was weniger Zeilen hat?

    ok, mach ich mal.



  • @lemon03 sagte in float vs double:

    Gibts da ein Tool vor, oder kann ich das online machen, wie es manchmal gezeigt wird?

    "wie es manchmal gezeigt wird?" ?
    Codebolt ist ganz praktisch, um verschiedenen Compilern beim Arbeiten zuzusehen. Färbt auch praktischerweise Sourcecodezeile und zugehörige Assembleranweisungen in der selben Farbe ein.

    @lemon03 sagte in float vs double:

    Und wenn ich jetzt gar keine Ahnung von Assembler habe, schaue ich dann einfach, was weniger Zeilen hat?

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



  • 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!


Anmelden zum Antworten