Schreiben in Ascii optimieren



  • Hallo liebe Forengemeinschaft!

    Ich steht vor einem kleinen Problem. Was ich machen will:
    Aus 47 einzelnen Ascii-Files per c++ genau eins machen! Jede dieser Ascii-Files hat Rund 1.000.000 Zeilen mit jeweils 3 Einträgen (Zeit, 1. Wert, 2. Wert). In die Outputdatei sollen dann 4 Einträge geschriebne werden (angepasste Zeit, 1. Wert, 2. Wert, Nummer des ursprünglichen Files).

    Soweit so gut - das klappt bei mir auch, dauert nur blöderweise über eine halbe Stunde. Deshalb meine Frage - kann ich meinen Schreibvorgang irgendwie optimieren?

    Mein Code (bzw. die wichtige sequenz):

    std::ofstream outfile;												 // Output-Datei intiallisieren
    	sprintf(filename,"%s_all_unsort.list",run);							// Name Output Datei
    	outfile.open (filename, ios_base::app);							   // Öffnen Output-Datei mit Option "Anhängen"
    
    	// Schreiben aller Listen in eine Datei
    	std::cout << "Schreiben der Liste " << filename << " läuft - bitte warten \n" ;
    	for (int i=0; i<(channel_max+1); i++)
    		{	std::ifstream infile;							// Input-Datei initialisieren
    
    			if (i<10)									   // Name für Infile festlegen			
    				sprintf(filename,"%s_ch0%d.list",run,i);
    			else
    				sprintf(filename,"%s_ch%d.list",run,i);
    
    			infile.open (filename);						  // Infile Öffnen 
    
    			std::cout << "Einlesen der Datei " << i << " von " << channel_max << " - Bitte warten... \n";
    
    			if (i==0||i==8||i==16||i==24||i==32||i==40) // diese Files sollen nicht eingelesen werden!
    				check=1;
    
    			while (check<1)
    			{	double a=0,b,c;							
    				infile>>a>>b>>c;						// Einlesen der Roh-Daten
    
    				if (i/8<1)								// Timestemp Anpassung
    					timestemp=a;
    				if (i/8<2 && i/8>0.99)
    					timestemp=a+t_offset12;
    				if (i/8<3 && i/8>1.99)
    					timestemp=a+t_offset13;
    				if (i/8<4 && i/8>2.99)
    					timestemp=a+t_offset14;
    				if (i/8<5 && i/8>3.99)
    					timestemp=a+t_offset15;
    				if (i/8<6 && i/8>4.99)
    					timestemp=a+t_offset12; 
    
    				if (a>0)
    				{outfile << setprecision (15) << timestemp << "\t" << b <<"\t"<< c << "\t\t" << i << "\n" ;}	// Schreiben der Outputdatei Reihenfolge [ t sg lg Detektornummer ]
    				else
    					{check=1;} 
    			}
    
    			infile.close();
    			check=0;
    	}
    	outfile.close();
    

    Wäre super, wenn ihr mir helfen könntet!

    Grüße



  • Entfern alle Ausgaben, die in einer Schleife stattfinden.
    Nutzt statt sprintf liber std::string

    Und achte darauf, dass du die Datei im Release-Modus kompilierst und auch ausführst.

    Den eigentlichen Algorithmus hab ich mir jetzt nicht angesehen.

    Ansonsten musst du herausfinden, ob die Zeit tatsächlich in diesem Programmteil verloren geht...



  • Hey,

    ich kann es leider nicht als string definieren. das ganze soll per root ausgeführt werden (bzw. wird es damit ausgeführt) und da kann ich leider keine strings verwenden 😞



  • Dein letzter Post macht keinen Sinn.

    Die Privilegien eines Prozesses haben nichts mit den Features einer Sprache zu tun.



  • Hallo Lummel202,

    Du hast eine ziemlich große Menge Daten zu lesen und zu schreiben. Die 47 Dateien müssten so jede ca. 20MB groß sein. Ich rechne pro MB formatiertes lesen etwa 1s - was nicht viel ist. Macht 20s pro Datei - macht knapp 16min nur für das Lesen.
    Du schreibst etwa die gleiche Menge - Schreibgeschwindigkeit ist - wenn überhaupt - 2mal schneller als lesen. Macht alles zusammen ca. 24min ... so über den Daumen, ohne die wahre Größe Deiner Dateien und die Leistungsfähigkeit Deiner Festplatte zu kennen.

    Das einzige, was mir noch einfällt, ist auf das Parsen und Schreiben der double-Werte zu verzichten.
    Du kannst die Zeile 28/29 ändern in:

    {   double a=0; std::string rest_der_zeile;                        
                    getline( infile >> a >> ws, rest_der_zeile );                        // Einlesen der Roh-Daten
    

    und dann entsprechend Zeile 50

    {outfile << setprecision (15) << timestemp << "\t" << rest_der_zeile << "\t\t" << i << "\n" ;}
    

    ... probier's mal aus und berichte von Deinen Erfahrungen.

    Gruß
    Werner



  • Werner:
    Wäre hier nicht wieder ein guter Trick statt operator>> eine eigene Version zu schreiben, die nur ein einzelnes sentry-Objekt pro Lesevorgang aus der Datei erzeugt? In deiner Version hast Du ja auch ein sentry pro Zeile. Könnte man nicht sogar eine Funktion schreiben, die gleichzeitig für Output und Input ein sentry erzeugt und die Zeichen dann gar nicht zwischenspeichert, sondern den sget() direkt sput()ted?

    Vielleicht ist das für OP zu viel und es geht hier auch nicht um meine Frage, aber in die Richtung hätte ich jetzt gedacht.



  • Hallo Eisflamme,

    Eisflamme schrieb:

    Werner:
    Wäre hier nicht wieder ein guter Trick statt operator>> eine eigene Version zu schreiben, die nur ein einzelnes sentry-Objekt pro Lesevorgang aus der Datei erzeugt? In deiner Version hast Du ja auch ein sentry pro Zeile. Könnte man nicht sogar eine Funktion schreiben, die gleichzeitig für Output und Input ein sentry erzeugt und die Zeichen dann gar nicht zwischenspeichert, sondern den sget() direkt sput()ted?

    Vielleicht ist das für OP zu viel und es geht hier auch nicht um meine Frage, aber in die Richtung hätte ich jetzt gedacht.

    Ich weiß schon auf welchen Thread Du Bezug nimmst. Na ja - mach' Du mal, ich gehe jetzt in's Bett.

    Ansonsten - beachte den Kommentar zu meinem eigenen Beitrag.
    Gutes Nächtle 😉
    Werner



  • Puh, also ich habe für den Anfang Mal eine copy-Funktion erstellt, das Parsen muss man jetzt natürlich noch komplett reinstecken.

    #include <iostream>
    #include <sstream>
    #include <fstream>
    #include <ctime>
    
    using namespace std;
    
    template<typename E_in, typename Traits_in, typename E_out, typename Traits_out>
    void fastCopy(basic_istream<E_in, Traits_in>& in, basic_ostream<E_out, Traits_out>& out)
    {
    	typedef basic_istream<E_in, Traits_in> istream_t;
    	typedef basic_ostream<E_out, Traits_out> ostream_t;
    
    	istream_t::sentry ok_in(in);
    	ostream_t::sentry ok_out(out);
    
    	if(ok_in && ok_out)
    	{
    		ios_base::iostate state_in = ios_base::goodbit;
    		ios_base::iostate state_out = ios_base::goodbit;
    
    		try
    		{
    			const std::ctype<E_in>& ctype_in = use_facet<std::ctype<E_in>>(in.getloc());
    
    			for(;;)
    			{
    				Traits_in::int_type m = in.rdbuf()->sbumpc();
    
    				if(Traits_in::eq_int_type(m, Traits_in::eof()))
    					break;
    
    				out.rdbuf()->sputc(m);
    			}
    		}
    		catch(...) // unschön, aber ich konnte nicht feststellen, dass sbumpc() nicht throwen kann
    		{
    			state_in |= ios_base::badbit;
    			if(in.exceptions() & ios_base::badbit)
    				throw;
    
    			state_out |= ios_base::badbit;
    			if(out.exceptions() & ios_base::badbit)
    				throw;
    		}
    
    		in.setstate(state_in);
    		out.setstate(state_out);
    	}
    }
    
    // ===================================================
    
    int main()
    {
    	time_t start = time(0);
    
    	ifstream ifs("C:/SomeBigFile.pdb", ios::binary);
    	ofstream ofs("C:/SomeBigFile.pdb.copy", ios::binary);
    
    	fastCopy(ifs, ofs);
    
    	cout << "Time difference: " << difftime(time(0), start) << " seconds";
    
    	int x;
    	cin >> x;
    }
    

    Das benötigt für 79 MB im Release 3 Sekunden, mein Windows-Explorer ist nicht schneller. Natürlich ist das nur ein Performancetest, weil jetzt überhaupt noch nichts geparst oder berechnet wird (ich glaube jedoch, der Flaschenhals wird nicht die Berechnung sein, das Parsen in einen double für a muss man vermutlich schon irgendwie optimieren).

    Aber man kriegt anscheinend nicht raus, ob der throw vom istream oder ostream kam, jedenfalls kommt die exception von ios_base und hat nicht wirklich viele Informationen, also sollte man die Streams nicht mit Exceptions nutzen?! Anscheinend ist das, was ich hier gemacht habe, nicht vorgesehen. Aber spricht doch eigentlich nicht viel gegen, oder?



  • ein echter parser ist bei sowas viel viel viel schneller als iostream. Eine Größenordnung schneller sollte machbar sein.



  • War das bezugnehmend auf meinen Post? Wie soll das Ding denn schneller als das BS sein? Die Geschwindigkeit wird offensichtlich durch die Lese/Schreibgeschwindigkeit der Festplatte gedrosselt. Und wir hatten hier schon so einige Diskussionen, dass iostreams langsam sein sollen. Das stimmt aber einfach nicht, wenn man nicht get/put, operator<<, operator>>, write or read benutzt, sondern die low-level sget/sput/...-Funktionen. Dass in dem speziellen Fall Streams weniger Nutzen haben, ist natürlich ein wenig was anderes (trotzdem kann man immer noch diverse i/ostreams nutzen).

    Ich habe gerade leider keine SSD, um meine Drosselungsthese zu überprüfen...



  • Ich weiß nicht wie gut diese ganzen low-level-System-Aufrufe sind, aber vielleicht beschleunigt sich das ganze, wenn man die komplette Datei zuerst in den Speicher ließt, einen stringstream draus macht und auf dem dann arbeitet... Und das Ergebnis ebenfalls einen stringstream schreibt. Kann mir vorstellen, dass durch das einsparen der ganzen read/write-Aufrufe einiges an Performance herauszuholen ist.



  • Genau, man hantiert mit 47 x 20MB im Arbeitsspeicher herum...



  • Das macht nichtmal 1gb. Das ist heutzutage doch kein Problem mehr.



  • Na ja, streng genommen ist das doppelte, man schreibt ja auch.

    Ich finde es trotzdem egal. Ich würde nach wie vor pro Lese-Datei einen ifstream anlegen. Und meine Variante erfordert ja auch, dass man pro ifstream einen ofstream mit ios::app anlegt. Dann hat man also immer nur eine Datei auf einmal im Speicher. Das wird die Performance nicht wirklich drücken.

    Und das in einen stringstream hauen bedeutet, dass man den gesamten Kopiervorgang verdoppelt. Anstatt alles "auf dem Weg" zu lesen, kopiert man es in den Speicher und parst dann dort wiederum durch. Ich bin nicht sicher, ob man dadurch irgendwelche Page/Cache-Misses einschränkt... aber das Performance-Problem mit operator<</>> liegt einfach darin, dass für jeden Vorgang ein sentry-Objekt angelegt wird, das teure Dinge macht, siehe den von Werner verlinkten Thread, hier verlinkt mit dem Post, in dem er es begründet: http://www.c-plusplus.net/forum/p2267872#2267872

    Legt man nur zwei sentrys (pro istream/ostream) pro Datei-Parse-Vorgang an, boosted man die Performance also automatisch. Denn sputc macht außer eof-Abfrage und Schauen, ob der Buffer noch ok ist, eigentlich nichts außer das Zeichen in den Buffer zu schreiben, jedenfalls in meiner MSVC-Implementierung.

    Nächste Frage ist vermutlich, ob die Facette zum Parsen von double schnell genug ist. Falls von Werner keine grundsätzlichen Einwände kommen und OP daran Interesse hätte, weil er noch nichts Besseres fand, versuche ich das später vielleicht auch Mal. 😋



  • Hey also erstmal danke für die schöne Diskusion, blicke aber noch nicht wirklich durch, ob es jetzt schneller geht 😉

    Bzgl. SSD - habe es gerade auf SSD ausprobiert und es dauert genauso lang 😞

    Bin leider auch noch in meinen C++ Anfängen.

    Grüße

    Sorry hatte die 2. Seite nicht gesehen:

    Hierzu: Das Programm sollte unter root von Cern laufen, daher kann ich leider keine stringsteams benutzen, da das irgendwie nicht unterstützt wird. Ist es den so viel schneller als Stream? Dann würde ich halt doch ein Standalone bauen.



  • Was heißt "irgendwie nicht unterstützt"? ostream/istream funktionieren, stringstream aber nicht? sstream hast Du aber inkludiert? Was ist denn die Fehlermeldung?



  • hey,

    also das problem ist, wenn ich das program mit stringstreams ausführe in Visual Studio, funktionierte es problemlos. Habe den code dann kopiert und in der Uni mit root gestartet, und es hat nicht funktioniert. Habe es daraufhin in sprintf umgeschrieben und schon gehts. (Zuhause Windows in der Uni Linux) Fehlermeldung an weiß ich nicht. Irgendwas mit Can't open ....!



  • Can't open or load the PDB-file?



  • Hey,

    also habs gerade mal mit einem anderen Code getestet:

    Code:

    #include <fstream> 
    #include <iostream>
    #include <vector>
    // #include "H1.h"			/* wird fuer root-Histogramme benoetigt */
    // #include "TFile.h"			/* wird fuer root-Histogramme benoetigt */
    // #include <TTree.h>			/* wird fuer root-Trees benoetigt */
    //#include "TBranch.h"
    // #include "TBasket.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <iostream>
    #include <fstream>
    #include <cstdlib>
    #include <string>
    #include <math.h>
    #include <sstream>
    #include <TH2.h>
    #include <TStyle.h>
    
    // #include<TTree.h>
    
    int main()
    { 
    	double a,b,c,Channel;
    	std::stringstream filename,channelnumber;
    	filename<<"run00029_ch"; //Eintragen des Ladeverzeichnisses in der Form "%%\\run00001_ch
    	std::cout << "Welcher Channel soll geplottet werden? \n" ;
    	std::cin >> Channel;
    	channelnumber << filename.str() << Channel;
    
    	std::vector<double> x;
    	std::vector<double> y;
    	std::vector<double> t;
            std::ifstream myFile; 
            myFile.open (channelnumber.str());   
     // -----------------------root Tree erstellen
    	char rootTreeName[60];
    	char* rohdatname="Testmessung";
    
    	sprintf(rootTreeName,"%s.channel35.root",rohdatname);		// Name fuer Root Tree-Datei festlegen 
    	TFile *hfile = new TFile(rootTreeName,"RECREATE");			
    	TTree *tree = new TTree("MyTree","An example of a ROOT tree");	// Tree erstellen 
     	tree->Branch("sg", &b ,"b/D");
    	tree->Branch("lg", &c, "c/D");
    
    //------------------------- root Tree füllen
    	for (int i=1; i<1000000; i++)
    	 {
    			double a=0,b,c;
            		myFile>>a>>b>>c; 
    			x.push_back(b);
    			y.push_back(c);
    			t.push_back(a);
    			if (a<1) 
    			{break;}
    
    	tree->Fill();
    
     	} 
            myFile.close(); 
    	hfile->Write();
    	hfile->Close();
    
    }
    

    Fehler ist:
    Error: Can't call basic_ifstream<char,char_traits<char> >::open(channelnumber.str()) in current scope test.cpp:39:
    Possible candidates are...
    public: void basic_ifstream<char,char_traits<char> >::open(const char* s,ios_base::openmode mode=ios_base::in);

    Im compiler geht es aber 😞



  • Du musst zum Öffnen der Datei vor C++11 einen C-String übergeben (*.str().c_str()).
    Der andere Compiler hatte offenbar C++11, dort geht es auch mit einem std::string.


Anmelden zum Antworten