1,000,000 floats einlesen



  • Hi,
    wie der Titel schon sagt geht es um das Einlesen von floats aus einer Textdatei.

    Die C++ Stream Klassen scheinen dabei ja irgendwie wesentlich langsamer zu sein als zB. fscanf(), und erst recht als fread() + strtod. Woran liegt das?

    Kann mir einer mal den schnellsten (+schönsten) Weg zeigen einfach 1,000,000 floats aus einer Textdatei zu lesen? (Am besten in einen std::vector<float>)
    Denn mit Filestreams wäre das einzige was mir so einfällt eigentlich das:

    float buf;
      while (is >> buf)
        buffer[i++] = buf;
    

    Nur das ist, wie gesagt, ziemlich lahm.



  • Bist du sicher, daß die Zuweisung in den Buffer so funktioniert? Dazu müsste der vector vorher groß genug sein für den Datei-Inhalt.



  • Bei deiner Methode muss die Grösse ja vorher sowieso schon bekannt sein. Dann kannst du den std::vector schon mal darauf vorbereiten ( std::vector::reserve );



  • Der schönste Weg? Ist wohl Definitionssache.

    #include <algorithm>
    #include <iterator>
    #include <vector>
    #include <fstream>
    
    //...
    std::vector<float> foo;
    foo.reserve(1000000);
    
    std::ifstream fin("file");
    copy(std::istream_iterator<float>(fin), std::istream_iterator<float>(), back_inserter(foo));
    

    Es ist auch der am schnellsten zu tippende (glaub ich) - und falls mal 1000001 Elemente einzulesen sind, funktioniert es auch noch.



  • Ich fürchte,

    copy(std::istream_iterator<float>(fin), std::istream_iterator<float>(),back_inserter(foo));
    

    ist nicht so arg viel schneller zu tippen als

    float x;
    while(fin>>x)
       foo.push_back(x);
    

    . Deswegen lasse ich copy mal zu Hause.

    Und das reserve ist überbewertet, denke ich.



  • Ja, es ging schon primär um die Geschwindigkeit bei der Ausführung, und da bekomme ich so leider exakt die selben Ergebnisse wie mit dem oben genannten weg 😞

    @CStoll @EOutOfResources
    Ja, die Größe ist vorher bekannt. Die Rechenzeit geht aber definitiv bei der Umwandlung String->Float verloren, alles andere ist eigentlich zu vernachlässigen. Und funktionieren tut das alles, es geht nur um die Geschwindigkeit 😃
    Denn der "C++ Weg" ist halt einfach mehr als 3 mal langsamer als der "C Weg". Jetzt bleibt zu klären ob das halt wirklich so ist, oder ob das nur daran liegt, dass ich mit C einfach mehr Erfahrung als mit C++ habe.



  • Der Geschwindigkeitsverlust liegt hier fast ausschließlich in den Locales. Da können die floats eben auch ein Komma als Trennzeichen haben, das können die C-Funktionen nicht. Leider weiß ich jetzt aber nicht, was der beste Weg ist, darauf zu verzichten, solltest du das nicht brauchen. Eine Möglichkeit wäre, Strings einzulesen und diese mit C-Funktionen umzuwandeln. Hübsch wäre auch boost::spirit , da ist das ein Einzeiler.


  • Administrator

    #include <ctime>
    #include <fstream>
    #include <iostream>
    
    int main()
    {
      std::ifstream input("data");
      float* data = new float[1000000];
    
      clock_t start = std::clock();
    
      for(int i = 0; i < 1000000; ++i)
      {
        input >> data[i];
      }
    
      clock_t end = std::clock();
    
      double duration = end - start;
      duration /= CLOCKS_PER_SEC;
    
      std::cout << duration << std::endl;
    
      delete[] data;
    
      return 0;
    }
    
    #include <time.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
      FILE* input = fopen("data", "r");
      float* data = (float*)malloc(sizeof(float) * 1000000);
    
      clock_t start = clock();
    
      for(int i = 0; i < 1000000; ++i)
      {
        fscanf(input, "%f", &data[i]);
      }
    
      clock_t end = clock();
    
      double duration = end - start;
      duration /= CLOCKS_PER_SEC;
    
      printf("%f", duration);
    
      free(data);
    
      return 0;
    }
    

    Der C++ Code läuft bei mir unter GCC schneller als der C Code. Und zwar fast doppelt so schnell ...
    Wahrscheinlich mache ich um die Uhrzeit irgendeinen offensichtlichen Fehler? 😕

    gcc (TDM-2 mingw32) 4.4.1
    g++ -O3 -o testCpp.exe test.cpp
    gcc -std=c99 -O3 -o testC.exe test.c
    

    Grüssli


  • Mod

    fscanf ist nicht gerade dir schnellste Funktion. Schließlich muss es im Gegensatz zu einem Stream-Operator den Formatstring und die Eingabe parsen. Das ist bei einfachen Formaten wie float ungefähr doppelt so viel Aufwand.



  • Cooky könnte ja mal seine beiden Code-fragmente zeigen. Würde mich interessieren 🙂



  • Cooky könnte ja mal seine beiden Code-fragmente zeigen. Würde mich interessieren 🙂

    [s]#define MYSIZE 1000000[/s]
    static const int MYSIZE = 1000000
    
    void ReadFloatsWithC(const char* fileName)
    {
      using namespace std;
      time_t t1;
    
      t1 = clock();
      FILE *fileIn = fopen(fileName, "rb");
      cout << "Time to open: " << clock() - t1 << endl;
    
      vector<float> floatBuf(MYSIZE);
      vector<char> fileBuf;
    
      t1 = clock();
      fseek(fileIn, 0, SEEK_END);
      long pos = ftell(fileIn);
      rewind(fileIn);
      fileBuf.resize(pos);
      fread(&fileBuf[0], 1, pos, fileIn);
      cout << "Time to read: " << clock() - t1 << endl;
    
      t1 = clock();
      char *tmpBuf = &fileBuf[0];
      for (int i = 0; i < MYSIZE; ++i)
        floatBuf[i] = strtod(tmpBuf, &tmpBuf);
      cout << "Time to parse: " << clock() - t1 << endl;
    
      t1 = clock();
      fclose(fileIn);
      cout << "Time to close: " << clock() - t1 << endl;
    
      int i;
      cin >> i;
      cout << floatBuf[i] << endl;
    }
    

    Ist jetzt einfach so aus meinem Testcode raus gefrickelt, sollte aber kein Fehler drin sein..

    Der C++ Code ist wohl eher uninteressant..
    fscanf läuft bei mir übrigens genau doppelt so schnell wie der C++ Code, aber der oben ist mit Abstand am schnellsten.
    Und ja, ich könnte das auch mit Filestreams einlesen, aber was genau habe ich davon wenn ich für die Umwandlung eh strtod brauche? 😃

    Edit x:
    Und wehe jemand kommt mir jetzt mit Stil, das ist nur schnell hingerotzter Code zum testen!! 😃

    Next Edit:
    @Dravere
    Komisch, habe beide Codes getestet (Visual Studio 2010, Release, Q6600@2,4GH) und C++ braucht bei mir ca. 1,9 Sek, C 0,8. Musste C etwas anpassen aber das sollte keinen Unterschied machen. Ich schreibe mal 3 Versionen und stelle die dann hier rein, das würde mich ja mal interessieren was andere da so für Werte bekommen.



  • So. Habe jetzt 3 Versionen, eine mit C++, zwei mit C. (fread + strtod, fscanf, ifstream)

    Hier der Code:
    C - fread + strtod:

    #include <stdlib.h>
    #include <stdio.h>
    #include <time.h>
    
    #define MY_FILENAME "test.txt"
    #define MY_FLOATCOUNT 1000000
    
    int main()
    {
      float *floatBuf;
      char *fileBuf, *tmpBuf;
      time_t t1;
      long i, len;
      FILE *file;
    
      t1 = clock();
      file = fopen(MY_FILENAME, "rb");
      printf("Time to open: %i\n", clock() - t1);
    
      floatBuf = malloc(MY_FLOATCOUNT * sizeof(float));
    
      t1 = clock();
      fseek(file, 0, SEEK_END);
      len = ftell(file);
      rewind(file);
      fileBuf = malloc(len);
      fread(fileBuf, 1, len, file);
      printf("Time to read: %i\n", clock() - t1);
    
      t1 = clock();
      tmpBuf = fileBuf;
      for (i = 0; i < MY_FLOATCOUNT; ++i)
        floatBuf[i] = strtod(tmpBuf, &tmpBuf);
      printf("Time to parse: %i\n", clock() - t1);
    
      t1 = clock();
      fclose(file);
      printf("Time to close: %i\n", clock() - t1);
    
      scanf("%i", &i);
      printf("%f", floatBuf[i]);
    
      free(fileBuf);
      free(floatBuf);
      return 0;
    }
    

    C - fscanf

    #include <stdlib.h>
    #include <stdio.h>
    #include <time.h>
    
    #define MY_FILENAME "test.txt"
    #define MY_FLOATCOUNT 1000000
    
    int main()
    {
      float *floatBuf;
      time_t t1;
      long i, len;
      FILE *file;
    
      t1 = clock();
      file = fopen(MY_FILENAME, "rb");
      printf("Time to open: %i\n", clock() - t1);
    
      floatBuf = malloc(MY_FLOATCOUNT * sizeof(float));
    
      t1 = clock();
      for (i = 0; i < MY_FLOATCOUNT; ++i)
        fscanf(file, "%f", &floatBuf[i]);
      printf("Time to parse: %i\n", clock() - t1);
    
      t1 = clock();
      fclose(file);
      printf("Time to close: %i\n", clock() - t1);
    
      scanf("%i", &i);
      printf("%f", floatBuf[i]);
    
      free(floatBuf);
      return 0;
    }
    

    C++ - ifstream

    #include <ctime>
    #include <fstream>
    #include <iostream>
    #include <vector>
    
    static const char *g_fileName = "test.txt";
    static const int g_floatCount = 1000000;
    
    int main()
    {
      using namespace std;
      time_t t1;
    
      t1 = clock();
      ifstream is(g_fileName);
      cout << "Time to open: " << clock() - t1 << endl;
    
      vector<float> buf(g_floatCount);
    
      t1 = clock();
      int i = 0;
      while (is >> buf[i++])
        ;
      cout << "Time to parse: " << clock() - t1 << endl;
    
      t1 = clock();
      is.close();
      cout << "Time to close: " << clock() - t1 << endl;
    
      cin >> i;
      cout << buf[i] << endl;
      return 0;
    }
    

    Meine Ergebnisse:
    Visual Studio 2010, Release (/MD). Q6600 (2.4GH)
    C - fread + strtod: ~560 ms
    C - fscanf: ~850 ms
    C++ - ifstream: ~1850 ms

    Jetzt bin ich ja mal gespannt, ob die ifstream Lösung bei irgendwem wirklich schneller läuft 😃

    Nachtrag:
    Mit gcc/g++ brauchen alle Programme wesentlich mehr Zeit! (-O3, -static-libgcc, -static-libstdc++)
    C - fread + strtod: ~900 ms
    C - fscanf: ~1600 ms
    C++ - ifstream: ~9200 ms (WTF 😕 )


  • Administrator

    VS 2010:
    C - fread + strtod: ~90 ms
    C - fscanf: ~160 ms
    C++ - ifstream: <15 ms (nicht mehr messbar)

    GCC 4.4.1 (TDM):
    C - fread + strtod: ~1020 ms (<- wtf?)
    C - fscanf: ~210 ms
    C++ - ifstream: ~85 ms

    C++ führt auf meiner Maschine noch immer mit Abstand und ich muss sagen, die Ergebnisse überraschen mich auch. Einen so signifikanten Vorsprung durch C++ hätte ich nicht erwartet.

    Noch nicht abgeklärte Unterschiede:
    - Festplatte? (Hab eine ältere SSD)
    - Wie sieht die test.txt Datei aus? Ich habe einfach durch Leerzeichen getrennte float Zahlen per Random reingeschrieben.

    Grüssli


  • Mod

    Komisch, ich kann cookie451s Ergebnisse bestätigen. Compiliert mit GCC 4.4.3. Wobei bei mir der ifstream nicht so total eingeht, das Verhältnis ist eher 3:2.5:1.5. Ich habe auch mal versucht die while-Schleife durch eine for-Schleife zu ersetzen (wir wollen ja fair sein - beim C code wird nie geprüft ob das Lesen erfolgreich war), aber das machte 0 Unterschied.

    Maschine ist ein i7 mit einem RAID1 Festplattenverbund. Datei enthielt 1 Million floats zwischen 0 und 1 durch \n getrennt. Wobei die Datei aber vollständig im RAM lag, aufgrund des Caches.


  • Mod

    ca: 500ms:500ms:2000ms mit gcc 4.5.2 auf einem atom 330
    Die Zeiten hängen auch vom Wertebereich ab, die Relation is aber immer ungefähr 1:1:4


  • Mod

    Habe noch ein bisschen mit dem Code rumgespielt, und eine spirit-Variante hinzugefügt (Version 4):

    #include <boost/spirit/include/qi.hpp>
    #include <iostream>
    #include <string>
    #include <vector>
    #include <ctime>
    #include <fstream>
    #include <iostream>
    
    static const char *g_fileName = "test.txt";
    static const int g_floatCount = 1000000;
    
    int main()
    {
      using namespace std;
      time_t t1;
    
      t1 = clock();
      ifstream is(g_fileName);
      cout << "Time to open: " << clock() - t1 << "ns\n";
    
      vector<float> buf;
      buf.reserve(g_floatCount);
    
      t1 = clock();
      int i = 0;
      string s;
      while ( getline( is, s ) )
      {
         namespace sp = boost::spirit;
         sp::qi::phrase_parse(s.begin(), s.end(), *sp::qi::float_ , sp::ascii::space, buf);
      };
      cout << "Time to parse: " << clock() - t1 << "ns\n";
    
      t1 = clock();
      is.close();
      cout << "Time to close: " << clock() - t1 << "ns\n";
    
      cin >> i;
      cout << buf[i] << endl;
      return 0;
    }
    

    510ms:1080ms:1830ms:570ms



  • ich hab's auch mal probiert. Meine Ergebnisse sind

    Visual Studio 2008 Express Edition, Release
    C - fread + strtod: geht gar nicht!! die Schleife mit strtod endet nicht
    C - fscanf: ~690 ms
    C++ - istream: ~1510 ms
    C++ - boost.spirit: ~790ms

    und die 5. Variante, ist wie C++ mit istream, aber eigener num_get-facette für Float
    C++ - ifstream + eigenes num_get-Float: ~610ms

    // liest float ohne(!) Vorzeichen nur in Gleitkommadarstellung (kein Exponent); keine Überlaufprüfung
    class GetFloat : public std::num_get< char >
    {
    protected:
        typedef std::istreambuf_iterator< char > iterator_type;
    
        virtual iterator_type do_get( iterator_type first, iterator_type last,
            std::ios_base& /*iosbase*/, std::ios_base::iostate& state,
            float& val ) const
        { // get float from [first, last) into val
            bool ok = false;
            val = 0.f;
            float divisor = 1.f;
            for( bool vorkomma = true; first != last; ++first )
            {
                if( *first == '.' && vorkomma )
                {
                    vorkomma = false;
                    continue;
                }
                if( *first < '0' || *first > '9' )
                    break;
                ok = true;
                (val *= 10.f) += float(*first - char('0'));
                if( !vorkomma )
                    divisor *= 10;
            }
            val /= divisor;
            if ( !ok )
                state  |= std::ios_base::failbit;
            if( first == last )
                state |= std::ios_base::eofbit;
            return first;
        }
    };
    

    Einzufügen in 'C++ - ifstream' hinter Zeile 15

    is.imbue( locale( is.getloc(), new GetFloat ) );
    

    Gruß
    Werner



  • Ich habe jetzt bisher nur mal ifstream probiert: Habe in die Datei eine Millionen floats schreiben lassen von 0.1234 bis 999999.1234. Was ich bemerkt habe ist folgendes: Wenn ich in jede Zeile, einen Wert schreibe, dann bin ich bei ~1700ms, schreibe ich dir Werte jedoch fortfolgend hintereinander, benötigt er nur ~330 ms.

    Würd jetzt ja auch noch kurz die anderen Varianten prüfen, das muss aber bis morgen warten, denn ich schreibe morgen eine Mathe-Klausur und das geht jetzt natürlich vor 😉 😃

    Lg freeG



  • Bei so komischen Werten habe ich mir mal die Mühe gemacht 3 Versionen (statisch gelinkt) zu bauen und mit den test.txt Dateien hochzuladen. (Einmal mit Zeilen, einmal mit Leerzeichen getrennt)

    http://www20.zippyshare.com/v/88004697/file.html (VS 2010)

    Es ist doch irgendwie unwahrscheinlich, dass der gleiche Code auf verschiedenen Rechnern so unterschiedlich "gut" läuft. Jetzt bin ich ja mal gespannt 😃

    (Meine Testergebnisse ändern sich kaum, sind auch mit beiden .txt Dateien fast gleich)

    Werner Salomon schrieb:

    C - fread + strtod: geht gar nicht!! die Schleife mit strtod endet nicht

    Kannst du das Problem mal näher beschreiben? Ich kann mir nicht so recht vorstellen warum die Schleife nicht enden sollte 😃

    Edit:
    Hab das Ganze auch gleich mal auf meinen Netbook getestet. (Atom 1.8GH)
    fread+strtod: ~1555ms
    fscanf: ~3200ms
    ifstream: ~4750ms

    Hat sich also nicht wirklich viel geändert..

    Edit:

    Dravere schrieb:

    C++ - ifstream: <15 ms (nicht mehr messbar)

    Bist du dir sicher, dass in deiner Datei kein #INF steht? Dann wäre die C++ Variante nämlich die einzige die abbrechen würde. <15ms scheint dann doch irgendwie unrealistisch zu sein 😃



  • So folgende Werte hab ich nun:

    test.txt

    fread-strtod: ~620ms
    fscanf : ~1300ms
    ifstream : ~1830

    test2.txt

    fread-strtod: ~620ms
    fscanf : ~1290ms
    ifstream : ~1820ms

    So nun verhält es sich bei mir zwischen beiden Text-Dateien gleich.
    Bei meiner Version war jedoch ifstream mit der Datei wie deine test2.txt wesentlich schneller. Warum weiß ich leider auch nicht.

    Lg freeG


Anmelden zum Antworten