Geschwindigkeitsfrage Datei einlesen und konvertieren



  • Hallo Leute,

    ich hab von meinem Chef einen tollen Auftrag gefangen. Ein Kunde liefert und eine Datei mit Messdaten in folgenden Format.
    zahl1#zahl2# zahl3# .....zahlx#
    Je nach dem was anfällt schwankt die Dateigröße zwischen 50.000 und 500.000 Zeilen pro Tag. Der Kunde wollte jetzt nicht ausschließen das es auch noch größer wird.
    Die Werte sind ausschließlich ganzzahlen.
    Ich hoffe mal ich bin hier richtig, denn ich schreibe kein sauberes c++ und auch kein reines c. Und bevor jemand fragt. Nein wir sind keine Softwarebunde, nur wir mache im Zuge von Kundenwünschen auch schon mal kleine Softwareteile selbst. 😃
    Ich hab mir jetzt mal zum Spass eine Datei mit 1 Mio Fakedatensätzen gebaut und ein paar Szenarien durchlaufen lassen. Ablauf ist bei mir aktuell immer alles einlesen (zeilenweise) und dann das Ergebnis zerlegen.
    Erste Idee das einlesen mit std::ifstream und getline in ein std::string Array. Funktioniert superschnell 1Mio Zeilen in 6 Sekunden.
    Danach zerlege ich das Array und konvertiere die einzelnen Teile in int. Die Ergebnisse werden in einer eigenen Struktur gespeichert die ich mit einen vector benutze. Naja was soll ich sagen stoi hat stringstream mehr als deutlich geschlagen. Aber selbst stoi hat mehr als 63 Sekunden für 1 Mio Datensätze gebraucht. Mein Chef war nicht zufrieden mit der Lösung und ich auch nicht, denn ein Array ist zu unflexible oder ich mach es so groß das die Konvertierungsroutine einfach zuviel RAM frist.
    Jetzt dacht ich mir lese ich die Daten in eine vector ein. Das geht auch schick dauert aber beim Einlesen 150 sec. Ist das anpassen der größe des vectors wirklich so eine riesen Bremse?
    Kennt jemand noch ne Möglichkeit wie ich das einlesen in den vector beschleunigt bekomme? Ach noch so zum Abschluss. Das zerlegen und konvertieren mit zwei vectoren ist deutlich schneller (20 Sek) als von array in vector (63Sek).


  • Mod

    Bist du der Ansicht, dass deine Beschreibung so präzise ist, um daraus zu rekonstruieren, wie der jeweils getestete Code exakt aussieht und auch für jeden klar werden kann, wo Verbesserungsmöglichkeiten bestehen?



  • Du hast uns gesagt, dass du die Daten einlesen musst und wohl irgendwo zwischenspeichern musst. Was genau musst du denn mit den zwischengespeicherten Daten anfangen?



  • Welchen Sinn hat es, die eingelesenen Zeilen zu speichern?



  • manni66 schrieb:

    Welchen Sinn hat es, die eingelesenen Zeilen zu speichern?

    Das kommt dann wohl auf den Anwendungszweck an.



  • 567546546 schrieb:

    manni66 schrieb:

    Welchen Sinn hat es, die eingelesenen Zeilen zu speichern?

    Das kommt dann wohl auf den Anwendungszweck an.

    Ach



  • 1. Warum der Umweg über getline/string/stoi, du könntest doch auch gleich int lesen. Und dann ein einzelnes Zeichen, das dann # oder \n sein muss.
    2. Was machst du mit den Daten überhaupt? Du hast mehrere Zahlen pro Zeile und mehrere Zeilen pro Datei. Soll da ein vector<vector<int>> rauskommen oder kannst du gleich etwas berechnen?
    3. Oder haben gar alle Zeilen dieselbe Anzahl Zahlen drin und Spalten.size == klein? Dann kann es sinnvoll sein, alles in einem vector<int> v(nSpalten*nZeilen) zu speichern (sofern die Daten denn alle gespeichert werden müssen).

    Aber "Das kommt dann wohl auf den Anwendungszweck an.".



  • @camper Dachte ich eigentlich schon aber ich versuch mich zu bessern 🙂 Mir geht es nicht um falschen Code sondern um Erfahrungswerte, denn ich kann mir gerade nicht vorstellen warum

    [b]globle Definition[/b]
    std::vector<std::string>	m_v_zeilen;
    
    [b]loakle Definition[/b]
    std::string l_str;
    std::ifstream l_importdatei("Beispieldatei.dat", std::ios_base::binary);
    while ((l_importdatei.good()) && (getline(l_importdatei, l_str))){ m_v_zeilen.push_back(l_str); }
    l_importdatei.close();
    

    soviel langsamer ist wie

    [b]globle Definition[/b]
    std::string m_v_zeilen[1000000];
    
    [b]loakle Definition[/b]
    std::string l_str;
    std::ifstream l_importdatei("Beispieldatei.dat", std::ios_base::binary);
    int i= 0;
    while ((l_importdatei.good()) && (getline(l_importdatei, m_v_zeilen[i]))){ i++;}
    l_importdatei.close();
    

    Ich hoffe mit den Beispiel gefällt dir die frage besser 😃

    @67695563 Das einlesen ist notwendig, da die Daten aus einen autarken System abgezogen werden das aus Sicherheitsgründen nicht am I-Net hängt. Das System ist nicht mehr das jüngste und liefert alles als Textdatei. 😞

    @manni66 Wo hab ich geschrieben das ich die eingelesenen Zeilen speichere? Ich hab geschrieben das ich eine Datei einlese und die Daten muss ich ja erstmal in eine Datenstruktur schieben. 🙂



  • Ich wette, dass du noch andere Dinge machst bzw. uns verschweigst. Wie schaffst du es sonst, 60 Sekunden für das Einlesen zu brauchen? Oder sind deine Zeilen extrem lang? Ich bin jetzt mal von 80 Zeichen/Zeile mal 1 Mio Zeilen = 80 MB an Daten ausgegangen. Das sollte in unter einer Sekunde passieren (zumindest wenn das alles im Cache ist).



  • Julian90 schrieb:

    @manni66 Wo hab ich geschrieben das ich die eingelesenen Zeilen speichere? Ich hab geschrieben das ich eine Datei einlese und die Daten muss ich ja erstmal in eine Datenstruktur schieben. 🙂

    Wozu? Was muss mit den Daten gemacht werden? Die Frage von manni66 zielt doch offensichtlich darauf, dass es auf die genaue Anforderung ankommt, ob die Daten alle gleichzeitig im Speicher verfügbar sein müssen 🙄
    Außerdem bleibt die Frage, warum liest Du nicht gleich int-Werte ein, warum öffnest Du die Datei mit std::ios_base::binary ... was wird schließlich mit den int-Werten gemacht?



  • Damit du mal einen Richtwert hast, 1 Mio. Zeilen mit je 20 Zahlen einlesen/splitten/konvertieren dauert bei mir eine Sekunde, mit (unnötigem) vorherigen Speichern der Zeilen in einem vector 1.15 Sekunden. Mit etwas Optimieren (beim Splitten) sollte sich diese Zeit auf die Hälfte reduzieren lassen.

    Da läuft bei dir also etwas grundlegend schief, es sei denn, deine Zeilen enthalten tausende Zahlen. Naheliegende Idee: sind überhaupt Optimierungen eingeschalten und kompilierst du im Release-Modus?

    Außerdem ist std::stoi sehr ineffizient. boost::lexical_cast/atoi sind nur wenig schneller, ein vernünftiges stoi musst du also vermutlich selbst implementieren. Aber vorher sind da noch andere Baustellen.



  • Nachtrag: das mit std::stoi bezog sich auf gcc/libstdc++. Das kann bei anderen Implementierungen der Standardbibliothek natürlich anders aussehen.



  • Bzgl. Warum

    std::vector<std::string>	m_v_zeilen;
    

    soviel langsamer ist wie

    std::string m_v_zeilen[1000000];
    

    Viele vector::push_backs sind schlecht, da dauernd reallokiert werden muss.
    Verwende evtl. vector::reserve zur Optimierung.

    Edit: Vergesst es. Die Std-implementierung sollte eigentlich schlau genug sein um zu vielle unnötige Allocs zu verhindern.
    Siehe hierzu zB http://www.drdobbs.com/c-made-easier-how-vectors-grow/184401375



  • Wenn du einen Richtwert für die größe deines Vektors hast, benutze

    std::vector<std::string>    m_v_zeilen;
    m_v_zeilen.reserve(1000000);
    

    Damit reservierst du entsprechenden Speicher. Ansonsten muss für push_back immer entsprechend Speicher neu reserviert werden und dass dauert eine gewisse Zeit.

    Je nach dem, was du mit den Daten vor hast, musst du die Daten auch nicht in eine Daten Struktur speichern. Du liest die erste Zahl ein, berechnest mit der irgendwas, machst mit dem Ergebnis was auch immer dir gefällt und liest dann die nächste Zahl ein.

    //Edit: scrontch war schneller bezgl reserve



  • Schlangenmensch schrieb:

    Damit reservierst du entsprechenden Speicher. Ansonsten muss für push_back immer entsprechend Speicher neu reserviert werden und dass dauert eine gewisse Zeit.

    Das stimmt zwar, aber gerade bei Typen die trivial movable sind (wie std::string) handelt es sich hier um wenige Prozent Performanceunterschied.
    Ich tippe hier auf so etwas wie angeschaltene Debug-Iteratoren.



  • Julian90 schrieb:

    @manni66 Wo hab ich geschrieben das ich die eingelesenen Zeilen speichere? Ich hab geschrieben das ich eine Datei einlese und die Daten muss ich ja erstmal in eine Datenstruktur schieben. 🙂

    Da

    Julian90 schrieb:

    Erste Idee das einlesen mit std::ifstream und getline in ein std::string Array.

    Da

    Julian90 schrieb:

    [b]globle Definition[/b]
    std::vector<std::string>	m_v_zeilen;
    


  • wob schrieb:

    Wie schaffst du es sonst, 60 Sekunden für das Einlesen zu brauchen?

    Das sollte problemlos möglich sein, wenn man den Optimizer nicht einschaltet.



  • Es wurde ja schon angedeutet, aber ich zeige es mal im Code:

    std::vector<int> data;
    char dummy;
    std::ifstream in("file.dat");
    while (true)
    {
      int i;
      if (in >> i)
        data.push_back(i);
      in >> dummy;   // skip '#'
    }
    

    Also nicht zeilenweise einlesen und dann nochmal über atoi oder istringstream oder sonst was, sondern direkt aus der Datei lesen. std::ifstream ist genau so von istream abgeleitet wie istringstream.



  • tntnet schrieb:

    Es wurde ja schon angedeutet, aber ich zeige es mal im Code:

    std::vector<int> data;
    char dummy;
    std::ifstream in("file.dat");
    while (true)
    {
      int i;
      if (in >> i)
        data.push_back(i);
      in >> dummy;   // skip '#'
    }
    

    Wann hört die Schleife wohl auf? Was ist mit Fehlern?



  • manni66 schrieb:

    wob schrieb:

    Wie schaffst du es sonst, 60 Sekunden für das Einlesen zu brauchen?

    Das sollte problemlos möglich sein, wenn man den Optimizer nicht einschaltet.

    Mit welchem Compiler?

    Gerade mal getestet:
    Erzeugen der Datei:

    perl -E 'sub r{int(rand(100000000))} for $row(1..1000000){print r,"#" for 1..10; say r}' > 1-mio-lines.txt
    

    Testprogramm

    #include <string>
    #include <fstream>
    #include <iostream>
    #include <vector>
    
    int main() {
        std::vector<std::string> m_v_zeilen;
        std::string l_str;
        std::ifstream l_importdatei("1-mio-lines.txt");
        while (getline(l_importdatei, l_str)) {
            m_v_zeilen.push_back(l_str);
        }
        if (!l_importdatei.eof()) std::cout << "Kein eof!\n";
        l_importdatei.close();
        std::cout << m_v_zeilen.size() << '\n';
    }
    

    Also die Vector-Version.

    Und dann

    g++ -O0 -Wall -Wextra read.cpp
    time ./a.out          
    1000000
    ./a.out  0,17s user 0,06s system 99% cpu 0,226 total
    

    Weit weg von 60s.


Log in to reply