Datei laden und parsen (Optimierung)



  • Hi,
    ich will eine Fukntion zum einlesen von Dateien überarbeiten.
    Bei den Dateien handelt es sich um Messwertschriebe, die ich nicht beeinflussen kann.
    Mir geht es in erster linie darum, das Lesen der Dateien zu beschleunigen, da es z.Z. länger dauert als die späteren Berechnungen.

    Hier mal der Code für das lesen von FFT-Daten:

    bool CFFT_Data::read_fft_file(const char *filename, S_datei_fft &data)
    {
    	data.inhalt.clear();
    	bool in_dataset=false;
    	int saetze = 0, reihen = 1, counter=0;
    	char zeile[255], *token1, *token2, *token3, sep[] = " \t\n";
    	FILE *stream = NULL;
    	S_messwert_fft akt_data;
    	S_messreihe_fft akt_set;
    	list<S_micro_kalibrierung> akt_cal_set = micro_cfg;
    
    	if( (stream = fopen(filename,"rt")) == NULL )
    		return false;	//fehler bein öffnen (datei nicht vorhanden)
    
    	fgets( zeile, 255, stream );	//zeile lesen
    	while( !feof(stream) )
    	{
    		//zerlegen der eingelesenen zeile
    		token1 = strtok( zeile, sep );
    		token2 = strtok( NULL, sep );
    		token3 = strtok( NULL, sep );
    		if(token2 == NULL)	//probe auf messwert oder datensatznummer
    		{	//begin eines datensatzes
    			if(!in_dataset)	//wenn noch kein datensatz gelesen
    			{
    				//erster datensatz wurde gefunden
    				//alle alten daten löschen
    				akt_set.datensaetze.clear();
    				data.inhalt.empty();
    				in_dataset = true;
    			}
    			else	//wenn neuer datensatz gefunden
    			{
    				//aktuelle messreihe speichern und einen neue beginnen
    				akt_set.anzahl = saetze;	//anzahl der datensätze aus aktueller messreihe speichern
    				counter += saetze;  //zählrt aller datensätze erhöhen
    				akt_set.kalib_row = akt_cal_set.front();	//übergeben der Kalibriersätze für die Mikrofone
    		//akt_cal_set.pop_front();	//lösche den Ersten
    				if(akt_cal_set.size() > 1)	//wenn mehr als ein Mikrosatz vorhanden
    				{
    					akt_cal_set.pop_front();	//lösche den Ersten
    				}
    				data.inhalt.push_back(akt_set);	//aktuellen datensatz speichern
    				data.datei_info.push_back(akt_set.datensaetze.size());	//anzahl der datensätze aus aktueller messreihe speichern
    				akt_set.datensaetze.clear();
    				saetze = 0;
    				reihen++;
    			}
    		}
    		else  //wenn im datensatz
    		{
    			akt_data.freq = atof(token1);	//frequenz auslesen
    			akt_data.real = atof(token2);	//realteil
    			akt_data.imag = atof(token3);	//imaginärteil
    			akt_set.datensaetze.push_back(akt_data);	//speichern des datensatzes
    			saetze++;	//gelesene daten in aktueller messreihe speichern
    		}
    		fgets( zeile, 255, stream );	//zeile lesen
    	}
    	//speichern der letzten messreihe
    	akt_set.anzahl = saetze;
    	counter += saetze;
    		akt_set.kalib_row = akt_cal_set.front();
    	data.inhalt.push_back(akt_set);
    	data.datei_info.push_back(akt_set.datensaetze.size());
    	data.anzahl_datensaetze = counter;
    	data.anzahl_messreihen = data.inhalt.size();
    
    	fclose(stream);  //datei schliesen
    	return true;
    }
    

    Wie ihr seht ist es nicht gerade optimaler Code. Die Umstellung des Codes auf C++ bekomme ich noch hin.
    Nur bei den if-Blöcken habe ich so meine Probleme. Ich suche eine Möglichkeit diese zu ersetzen um so den Code etwas schneller zu machen.

    Hier noch ein Ausschnitt aus einer solchen Messwertdatei, evtl. sieht jemand von euch noch eine Möglichkeit zur Optimierung.

    1                                             //Messreihennummer
      0       0.003019087712                    0   //Offset
      2      -0.006272755701       -0.01370646872   //Frequenz Real- Imaginär-Teil
      4       0.001466225159       0.007759021394 
      6        0.00223540761     -0.0003204310803 
      8     -0.0004062494738    -3.375779664e-005 
      10    -3.019850332e-005      0.0001026067879 
    .....
      6396      0.0001561847993     -0.0003179971798 
      6398     -0.0001969154958      0.0001545309007 
      6400      3.06143794e-005    -2.377467639e-005 
      2                                             //Messreihennummer (neue Messreihe)
      0       0.000115732219                    0 
      2     -0.0002904215692      0.0004103402787 
      4      0.0003056752659     7.323408277e-005 
      6     -9.83728639e-005      -0.000631246431
    


  • Nur ein paar Tips:
    - Bau I/O-Streams ein
    - Statt char[] nimm std::string
    - Lies die Datei direkt in den Speicher und parse erst danach.



  • Ok, habe jetzt das lesen der Datei auf std::string umgestellt.
    Erstaunlich ist, dass das Lesen jetzt länger dauert. 😕

    Hier mal der Code:

    bool lies_datei(char *filename, int &count, list<messwert> &out)
    {
        FILE *stream;
        char zeile[256];
        string to_save;
        if( (stream  = fopen( filename, "r" )) == NULL )
            return false;
    
        fgets( zeile, 256, stream );
        while( !feof(stream))
        {
            to_save = zeile;
            fgets( zeile, 256, stream );
        }
        return true;
    }
    
    bool lies_datei(char *filename, int &count, list<string> &out)
    {
       fstream f;
       char tmp[256];
       string tmp_str;
       f.open(filename, ios::in);
       while (!f.eof())
       {
           f.getline(tmp, 256);
           tmp_str=tmp;
       }
       f.close();
       return true;
    }
    

    Ich habe nun beiden Funktionen die gleiche Datei gegeben und beide Funktionen 100mal aufgerufen. Dabei stellte ich fest, dass die Funktion mit fstream rund 5,5 mal mehr Zeit braucht. (RELEASE)

    Wieso????



  • du kannst die struktur der datei gleich in kombination mit dem >> operator von ifstream verwenden, der liest bis leerzeichen oder newline. dann noch mit einem stringstream für die zahlenumwandlung koordinieren:

    int messreihe;
    int freq;
    double realt, imagt;
    string zeichen;
    sstream wandler;
    ifstream infile("datensatz.tab");
    while (!infile.eof())
    {
        infile >> zeichen; // messreihe
        wandler << zeichen;
        wandler >> messreihe;
        infile >> zeichen; // frequenz
        wandler << zeichen;
        wandler >> freq;
        infile >> zeichen; // realteil
        wandler << zeichen;
        wandler >> realt;
        infile >> zeichen; // imteil
        wandler << zeichen;
        wandler >> imagt;
    
        deinstack.push(Messreihe(messreihe, realt, imagt));
    }
    

    so oder so ähnlich. hilft das was?

    😉 edit: man muss den stream nach jedem umwandeln irgendwie resetten, das krieg ich ich irgendwie nur nicht hin 😞



  • @Korbinian
    Hilft mir leider nicht, da ich jede Zeile prüfen muss, ob da ein oder drei Werte drin sind.
    Ist es nur ein Wert beginnt eine neue Messreihe und bei drei werten befinde ich mich in einer Messreihe.

    Trotzdem Danke.



  • Erstmal solltest du statt der char*-Version von getline() lieber die string-Version verwenden:

    getline(f,tmp_str);
    

    Und zweitens kannst du mit find_first_of() überprüfen, ob du ein Zeichen im String hast:

    getline(f,str);
    stringstream wandler(str);
    if(str.find_first_of(" \t")
    {
      //mehrere Werte
      wandler>>w1>>w2>>w3;
      if(!wandler)
      {
        cout<<"Fehler";
        break;
      }
      //...
    }
    else
    {
      //einzelner Wert
      wandler>>w0;
      //...
    }
    


  • Die frage ist, wie schnell du es wirklich brauchst ....

    gib mal ne Groessenordnung der Datei ? 🙂

    muss es plattformunabhaengig sein ?
    wenn nein, schau dir memorymapping an ... Datei-Block in speicher schieben, und dann mit zeigern draufrumrutschen ^^

    Fuer das parsen selber solltest keine Stringobjecte neu anlegen, sondern nur mit zeigerarithmethik suchen
    Fuer das wegspeichern der Tokens und werte dann kannst scho strings erstellen (std::string) Das spart dir kopien beim reinen parsen ....

    Ich wuerd mir aber erst mal an deiner stelle nen Interface fuer deinen Parser bauen, und die langsame aber sicher funktionierende variante mit lesen in die std::string Puffer nehmen ...
    Wenn dann alles ordentlich funktioniert, kannst das parsen nach oben benannter methode vielleicht immer noch beschleunigen. Vielleicht wenn sauber programmiert hasst, brauchst es ja gar nicht mehr ^^

    Prinzipiell ...
    Deine dateien scheinen eklig gross werden zu koennen, also viele Daten, aber die Struktur selber ist recht klein und uebersichtlich ....
    Also bietet sich nen parser mit eventschnittstelle grade zu an ....
    Damit trennst du das parsen von der datenhaltung und wirst noch flexiebler.
    Du brauchst aigentlich nur wenige events, also

    virtual void OnStart() = 0;
    virtual void OnStop() = 0;
    virtual void OnMessreihe(unsigned int iNr) = 0;
    virtual void OnOffset(unsigned int iNr, unsigned int iOffset) = 0;
    virtual void OnMeasurmentPoint(unsigned int iNr,double iValue1, double iValue 2) = 0;

    Sollte als Interface fast langen ....
    Dem Parser uebergibts nen Object mit der Schnittstelle und der befeuert dir das dann ...
    Aehnlich arbeiter SAX zum beispiel, und sowas iss eigentlich schon performant ^^

    Ciao ...



  • Würde so etwas versuchen. So musst du den string nicht direkt überprüfen.

    void read_file( istream & str ) {
       int nr;
       double mreal = 0.0;
       double mimag = 0.0;
       double r;
    
       string line;
       while ( getline( str, line ) ) {
          stringstream sstr(line);
          int cou = 0;
          sstr >> nr;
          while ( sstr >> r ) {
             switch ( cou ) {
                case 0: mreal = r; break;
                case 1: mimag = r; break;
                default : 
                     cout << "error parsing '" << line << "'" << endl;
                     break;
             }
             cou++;
          }
          if ( cou ) {
             cout << "datensatz " << nr << ":" << mreal << "," << mimag << endl;
          }
          else {         
             cout << "neue Messreihe " << nr << endl;
          }
       }
    }
    

    Kurt



  • Fuer den ersten entwurf und um sein programm erstmal zum laufen zu bringen, voellig ok.
    Aber steckt auch noch bisserl optimierungspotential drinne .

    void read_file( istream & str ) {
       int nr;
       double mreal = 0.0;
       double mimag = 0.0;
       double r;
    
       string line; 
       while ( getline( str, line ) ) { // Die komplette Zeile wird kopiert, nach paarmal ist der string hoffentlich gross genug ^^
          stringstream sstr(line); // Der string wird gleich in nen object umgebaut, und dazu noch total neu konstruiert, sehr sicher, aber auch sehr rechenintensiv ^^ 
          int cou = 0;
          sstr >> nr; // tolle idee, die std streamoperationen gleich zu nutzen.
          while ( sstr >> r ) {
             switch ( cou ) {
                case 0: mreal = r; break;
                case 1: mimag = r; break;
                default :
                     cout << "error parsing '" << line << "'" << endl;
                     break;
             }
             cou++;
          }
          if ( cou ) {
             cout << "datensatz " << nr << ":" << mreal << "," << mimag << endl;
          }
          else {        
             cout << "neue Messreihe " << nr << endl;
          }
       }
    }
    

    Wie gesagt, fuer kleinere Sachen sicher akzeptabel, fuer zig millionen mal aufrufen sicher optimierungsbeduerftig ... und je nach implementation der STL wirst hier sehr unterschiedliche ergebnisse erzielen ....

    Punkte wo man ansetzen kann ...

    1. deine zeilen sind eigentlich definiert max lang, zumindest die Puffer (std::string) von haus aus gross genug Konstruieren ....

    2. bei der sofortigen kopie das neuerstellen des stringstreams verhindern, sondern in den puffer des alten schreiben lassen ... damit hilfst der STL nen new(aufn eigenen allokator zwar) zu vermeiden. Bei guten impls wirds nich viel ausmachen, weil die allokatoren recht flott sind

    Wenn das immer noch zu langsam ist, wirst dem mit reinen C++ mitteln kaum noch zu leibe ruecken koennen.
    Was die sache "langsam" macht, sind die X-maligen kopien ...

    eine variante, waere c++ klassen zu verwenden, die cow (copy on write) koennen (nicht mehr STL, dafuer unsicherer, code bleibt aber lesbar. faellt mir auf anhieb die MFC und die QT ein)
    oder auf selber in die hand nehmen und der sache mit C-Funktionen und roher pointerarithmetic auf die pelle ruecken.

    Ciao ...



  • Habs ein wenig optimiert. Jetzt wird nicht mehr so viel kopiert.

    void read_file( istream & str ) {
       int nr;
       double mreal = 0.0;
       double mimag = 0.0;
       double r;
    
       stringstream sstr;
       char buffer[100];
       while ( str.getline( buffer, 100 ) ) {
          sstr.str(buffer);
          int cou = 0;
          sstr >> nr;
          while ( sstr >> r ) {
             switch ( cou ) {
                case 0: mreal = r; break;
                case 1: mimag = r; break;
                default : 
                     cout << "error parsing '" << buffer << "'" << endl;
                     break;
             }
             cou++;
          }
          if ( cou ) {
             cout << "datensatz " << nr << ":" << mreal << "," << mimag << endl;
          }
          else {         
             cout << "neue Messreihe " << nr << endl;
          }
          sstr.clear();
       }
    }
    

    Mein erstes Posting sollte eigentlich nur die Idee zeigen dass man die überprüfung der Eingabezeile auch den streamfunktionen überlassen kann.
    Kurt



  • Jo er soll mal die standard ausgabe auskommentieren und laufen lassen ...
    wenns dann zufriedenstellend schnell durchlaeuft, hat er doch was er braucht ^^

    Ciao ...



  • DANKE DANKE DANKE DANKE.
    Da sind wirklich tolle Ideen dabei und ich habe jetzt auch eine Idee wie ich das Umsetzen kann.

    @RHBaum
    Wegen der Frage nach der Größenordnung: Im Moment ist das ganze noch off-line, also ist die Zeit noch relativ unkritisch. Es sollen aber später Messreihen mit 16k-Samples pro Sekunde gelesen werden.



  • Du bekommst dann deine daten nich mehr per file sondern per Stream ?
    oder per Net I/O ?

    nich das die geschichte mit dem File dann zum umweg wird ?

    glaub nich dass nen Messystem in ne datei reinstreamt und sich drauf verlaesst dass du konkurrierend zugreifend den muell selber da rausraeumst ^^

    also rein vom IO wenns sauber gemacht ist, iss 16kbyte / s eher unkritisch , da codier ich ja noch mp3's im hinnergrund bei ^^
    ich mach hier teilweisse mit 1000kbaud rum, ok, aber ich krieg meine daten auch per shared memory und mit events ... aber das parsen sollte da nich so das prob sein. In nen temp file schreiben schon eher, weil das zeitverhalten unter nichtechtzeit BS nicht definiert ist

    Ciao ...



  • Das wird dann später zweigleisig.
    Aktuelle Daten per Stream und da das Messsystem die Daten in Dateien ablegt benötige ich das Laden ja auch, um ältere Aufnahmen zu betrachten.



  • daishi schrieb:

    Das wird dann später zweigleisig.
    Aktuelle Daten per Stream und da das Messsystem die Daten in Dateien ablegt benötige ich das Laden ja auch, um ältere Aufnahmen zu betrachten.

    In dem Fall solltest du auf jeden Fall die Stream-Version verwenden (andernfalls benötigst du zwei gesonderte Funktionen für's einlesen vom Netzwerk (vermutlich über ein istream-Derivat) und Datei (FILE*).



  • Das system die daten in so Textformate als log wegspeichern is durchaus ueblich

    Das messysteme die daten in streams in der selben form irgendwo rausstreamen, iss eher unueblich ^^

    meist haben die systeme ne API (C-Header + dll) wo ma die daten pollen muss, oder noch besser per event benachrichtigt wird ...
    die daten liegen dann meist in schoenen C-Structs vor, andernfalls wuerd ich dem hersteller an die Gurgel springen ^^
    In dem Fall wuerd ich mir zur uebertragung nen format(protokoll) suchen(bauen) wo ich nimmer "parsen" muesst ^^

    Naja, ich kenn aber nich alles was es aufm Markt gibt, waer aber ziemlich ungewoehlich ....

    Ciao ...



  • hier mal trick 17 mit selbst überlistung:

    void read_file( istream & str ) {
       //zwischenspeicher
       float array[4];
       int index=0;
    
       while(str){
          str>>array[index];
          ++index;
          if(index==4){
              //test
              //wenn eine neue messreihe anfängt, hat das array folgende form:
              //messreihenNummer|0|zahl|0
              //es gibt keine messwertkonstellation, bei denen die beiden                      
              //nullen an genau diesen stellen auftauchen könnten.
              //wenn der erste wert keine messreihenNummer ist, so geht der     
              //datensatz von element 0-2. element 3 gibt dann entweder die
              //nummer einer neuen messreihe, oder den ersten wert eines 
              //neuen datensatzes an. Da messreihen laut dateischnipsel mit 1 beginnen, und datensätze mit einer 2, bleibt nur der offsetwert übrig.
    
              if(array[3]==0){
                   messreihenNummer=array[0];
                   data1=array[1];
                   data2=array[2];
                   data3=array[3];
    
                   //index zurücksetzen, array komplett ausgewertet
                   index=0;
              }
              else
              {
                   //kein offset wert.
                   //die ersten 3 daten bilden einen messwert
                   data1=array[0];
                   data2=array[1];
                   data3=array[2];
    
                   //der 4. wert wurde "zu früh" gelesen, und wird an den anfang gesetzt
                   array[0]=array[3];
    
                   //da der erste wert gültig ist, index nicht auf 0 sondern 1 setzen
                   index=1;
              }
          }
       }
    }
    

    //edit mom ich teste das eben schnell :p
    //edit2 der testschnipsel vom threadstarter funzt. Kriegts jemand noch schneller? jetzt wär ich mal so richtig gespannt^^


Anmelden zum Antworten