Dateien schnell einlesen
-
Ich muss für ein Prüfprogramm eine große Dateien (und Datenmenge) einlesen. Das ganze soll möglichst schnell gehen.
Momentan verwende ich als "Anfangslösung" die C-Variante mit FILE, fopen, fread und einer Blockgröße von 8MB.
Da es sich aber um ca. 60GB an Daten handelt möchte ich die Performance erhöhen (die festplatten arbeiten nur mit ca. 30% ihrer Leistung.
Sind andere Varianten schneller? Wenn ja welche?
-
Du könntest mal versuchen, ungepuffert zu lesen:
setbuf(fp, NULL);Solange dein Programm als einziges intensiv von der Festplatte liest, könnte dies einiges an Performance bringen (anstatt daß immer wieder in einen temp.Puffer kopiert wird). 60GB sind jedoch eine Menge, dies wird jedoch immer noch einige Dutzende Minuten dauern...
P.S. Wie hast du denn bisher die Blockgröße angegeben?
-
Hallo
Vermutlich sind die WinAPI-Funktionen die effizientesten.
bis bald
akari
-
Dieser Thread wurde von Moderator/in akari aus dem Forum VCL (C++ Builder) in das Forum WinAPI verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
akari schrieb:
Vermutlich sind die WinAPI-Funktionen die effizientesten.
Habs getestet, synchrones Lesen mit ReadFile, ganze Datei reinmappen mit MapViewOfFile, asynchrones ReadFile mit 8 offenen OVERLAPPEDs.
Ergebnis: Synchrones Lesen mit ReadFile und ganze Datei reinmappen mit MapViewOfFile sind als gleich schnell anzusehen, asynchrones Lesen dagegen ist meßbare langsamer
.
Evtl ist zu Überlegen, ob es immer wieder MD5 sein muß, also gegen böswillige und absichtlich so berechnete Dateiveränderungen, daß man die Änderung nicht mehrkt, geschützt wird, oder ob CRC reicht, also gegen zufällige Veränderungen.
-
Die Variante mit synchronem Lesen dürfte meiner Ansicht nach auch die "einfachste" sein was die Programmierung betrifft.
CreateFile, Read File, und CloseHandle sind mir klar.
Nur eines frage ich mich gerade, wenn ich als Beispiel einen Puffer von 1MB übergebe, und ReadFile auf 1024*1024 Zeichen begrenze, wie kann ich nach diesem ersten Block das zweite MB der Datei lesen?@TH69
bisher habe ich fread einen Puffer mit 8MB Größe übergeben, und die Anzahl der zu´lesenden Zeichen auf 8*1024*1024 eingestellt.
-
AntonWert schrieb:
Die Variante mit synchronem Lesen dürfte meiner Ansicht nach auch die "einfachste" sein was die Programmierung betrifft.
Jo, wenn Du Streams verarbeiten kannst.
Wenn man die gesamte Datei sehen will oder NonCopyStrings benutzen will, zieht filemapping, das ist aber noch doof, weil's zu viele 32-bitter noch gibt.
Nur eines frage ich mich gerade, wenn ich als Beispiel einen Puffer von 1MB übergebe, und ReadFile auf 1024*1024 Zeichen begrenze, wie kann ich nach diesem ersten Block das zweite MB der Datei lesen?
Streamschnittstelle anbieten, schlage ich vor. Meine ist ein wenig unüblich (ein Experiment um Ranges), aber Du erkennst sicher, wie es geht, daß ich ohne Performanceverlust zeichenweise aus dem Stream lese udn der Stream sich immer 8k-Happen holt.
class InputStream{ private: virtual void underflow()=0; protected: ArrayReader<char> rawReader; public: char peek(){ return rawReader.peek(); } void pop(){ rawReader.pop(); if(rawReader.isEmpty()) underflow(); } bool isEmpty(){ return rawReader.isEmpty(); } ArrayReader<char> peekBuffer(){ return rawReader; } void popBuffer(){ underflow(); } }; class SyncFileReader:public InputStream{ private: Array<char,8192> buffer; os::File file; void underflow(){ Size bytesRead=os::readFile(file,buffer.getBegin(),buffer.getSize()); rawReader=ArrayReader<char>(buffer.getBegin(),buffer.getBegin()+bytesRead); } public: SyncFileReader(WChar const* fileName): file(os::openFileForRead(fileName)){ underflow(); } ~SyncFileReader(){ os::closeFile(file); } }; ... UInt64 foo3(){ MapFileReader in(L"D:/Downloads/wordlist.txt"); // SyncFileReader in(L"D:/Downloads/ihatemath.txt"); CRCHash crc; while(!in.isEmpty()){ crc.update(in.peek()); in.pop(); } return crc.crc; }Edit: Nein, ma erkennt wohl garnix.
Bau Dir halt eine Klasse, die den Puffer, das FileHandle und einen char* leseposition,ende hat undchar get(){ if(leseposition==ende){ nächstenBlockLesen; leseposition=start; ende=start+anzahlGelesen; } return *leseposition++; } //und noch irgendwie das Dateiende melden können
-
@volkard
deine Hilfe in Ehren finde es echt Super dass du ein Beispiel anbietest
Aber für mich (ich komme aus der Mikrokontroller-C-Ecke, ist diese "Hardcore"-Variante irgendwie unverständlich
Klar ich will mich schon durchbeißen und den Performancegewinn von ReadFile Testen, aber ich muss kleinere Schritte machen dass ich das verstehe.
Bin ich denn mitBOOL WINAPI ReadFile( __in HANDLE hFile, __out LPVOID lpBuffer, __in DWORD nNumberOfBytesToRead, __out_opt LPDWORD lpNumberOfBytesRead, __inout_opt LPOVERLAPPED lpOverlapped );noch auf dem richtigen Dampfer?
-
AntonWert schrieb:
@volkard
deine Hilfe in Ehren finde es echt Super dass du ein Beispiel anbietest
Aber für mich (ich komme aus der Mikrokontroller-C-Ecke, ist diese "Hardcore"-Variante irgendwie unverständlich
Klar ich will mich schon durchbeißen und den Performancegewinn von ReadFile Testen, aber ich muss kleinere Schritte machen dass ich das verstehe.
Bin ich denn mitBOOL WINAPI ReadFile( __in HANDLE hFile, __out LPVOID lpBuffer, __in DWORD nNumberOfBytesToRead, __out_opt LPDWORD lpNumberOfBytesRead, __inout_opt LPOVERLAPPED lpOverlapped );noch auf dem richtigen Dampfer?
Aber ja!
Die windows-Sachen hab ich nur weggekapselt, damit ich gleichnamige unix-Kapsel automatisch nehmen kann.File openFileForRead(FileNameChar const* fileName) { File file=CreateFile(fileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,NULL); if (file==INVALID_HANDLE_VALUE) throw 0; return file; }Size readFile(File file,char* buffer,Size size){ DWORD bytesRead; BOOL rc=ReadFile(file,buffer,size,&bytesRead,NULL); if(rc==FALSE) throw 0; return bytesRead; }
-
Aber es haben fopen und fread bereits so einen netten Puffer und sollten nur wenige Prozent unter dem Optimum liegen, wenn Du nicht gerade einzelne Bytes liest.
Blockgröße ab 8k ist gut. 16k ist etwas schneller, 32k nur noch minimal schneller. Bei Deinen 8M brauchst Du sicher nicht zu befürchten, es sei zu klein.
Was heißt, die Platten laufen auf nur 30%? Läuft dabei die CPU auf 100% oder weniger? Bei weniger liegts nicht an Deiner Programmierung, fürchte ich. Ist das Kopieren der Files denn spürbar schneller Dein Lesen?
-
volkard schrieb:
...
Was heißt, die Platten laufen auf nur 30%? Läuft dabei die CPU auf 100% oder weniger? Bei weniger liegts nicht an Deiner Programmierung, fürchte ich. Ist das Kopieren der Files denn spürbar schneller Dein Lesen?Wenn ich meinen Lesedurchsatz anschaue wird bei Verwendung meines Programmes nur ca 30% dessen genutzt was ich als Maximum mit anderen Programmen gesehen habe.
-----------------------------------
Das Kopieren der Datenen geht schneller, doch befürchte ich dass irgendwo im Hinterfrund ein Cache gefüllt wird, denn selbst die 2. Ausführung meines Programmes läuft schneller.
------------------------------------
@volkard
Geht es auch mit der Verwendung von Puffern, dass man zuerst einen Block mit ReadFile liest, diesen auswertet, und dann den nächsten ließt?
Oder anders formuliert: Wie kann ich nach einlesen des ersten Blockes (wenn mein Puffer zu klein war) den 2. Block lesen?
-
Ok, ich bastel Dir schnell was, was eine Checksumme berechnet.
-
#include <iostream> #include <windows.h> using namespace std; int main(){ HANDLE hFile; char buffer[65536]; hFile=CreateFile("D:/200MB.tmp",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN,NULL); if(hFile==INVALID_HANDLE_VALUE){ cout<<"Datei nicht da odwer so\n"; return 1; } char* end=buffer;//egal char* readPos=end;//gleich ende anzeigen, damit der erste block gleich geladen wird unsigned int chkSum=1; for(;;){ if(readPos==end){//wenn buffer leer, dann buffer einlesen // cout<<"read block\n"; DWORD bytesRead; BOOL rc=ReadFile(hFile,buffer,65536,&bytesRead,NULL); if(rc==FALSE){ cout<<"unbekannter fehler\n"; break; } if(bytesRead==0){ cout<<"fertig\n"; break; } readPos=buffer; end=buffer+bytesRead; } chkSum=chkSum*31+static_cast<unsigned char>(*readPos);//checksumme updaten ++readPos;//zeichen weiterschalten } cout<<"checksum: "<<chkSum<<'\n'; }Die Schleife ist so komisch, damit nur ein bedingter Sprung drin ist und trotzdem die Berechnungszeile die Daten byteweise kommen sieht, nicht blockweise.
-
Vielen Dank für das Beispiel volkard. Damit konnte ich meine Anwendung umbauen.
Sie ist nun etwas schneller als mit der klassischen C Variante und fopen, fread, fclose. Aber nur ca 4%.
Aus irgend einem Grund wird das Ganze nicht schneller, obwohl meine CPU nicht bei 100% ist. Keine Ahnugn warum....
-
Also bei mir hüpften 900M in wenigen 10 Sekunden rein, weil die 900M noch ins RAM passen und die Platte sich nicht zu bewegen brauchte. Bei 3800M mußte die Platte liefern und es war im Viele-Minuten-Bereich. Also die Verarbeitungszeit und die Funktionsaufrufe sind wohl völlig irrelevant.
Ist die Datei stark fragmentiert?
-
Hallo volkard,
zwei Dinge kommen noch zusammen, zum einen handelt es sich nicht nur um eine Datei, zum anderen sind nicht alle Dateien sehr groß.
Als ein Vergleich könnte recht gut eine zufällige Ansammlung der Dateien einer Festplatte dienen.
Kann ich bei keinen Dateien noch was optimieren?
-
AntonWert schrieb:
Hallo volkard,
zwei Dinge kommen noch zusammen, zum einen handelt es sich nicht nur um eine Datei, zum anderen sind nicht alle Dateien sehr groß.
Als ein Vergleich könnte recht gut eine zufällige Ansammlung der Dateien einer Festplatte dienen.
Kann ich bei keinen Dateien noch was optimieren?Zuerst würde mir einfallen, die Dateien auf der Platte zusammenzurücken mit MyDefrag http://www.mydefrag.com/
Oder, falls möglich noch besser, sie werden alle in der Reihenfolge, wie sie gelesen werden sollen, zusammenkopiert, und eine zweite Indexdatei (die später im RAM liegt) sagt, welcher Dateiname welchem Bereich innerhalb der großen Datei entspricht. (SetFilePointer).
Die große Datei kann dann mit Contig http://technet.microsoft.com/de-de/sysinternals/bb897428.aspx defragmentiert werden.
-
Mal in eine andere Richtung gedacht: Wie viele CPUs / Kerne sind im (Produktiv-) System vorhanden? Wenn es zwei oder mehr sind: Da es sowieso schon um mehrere Dateien geht, könnte man das Einlesen und Verarbeiten auf mehrere Threads verteilen?
-
Die API-Funktionen sind die schnellsten...
#define RET_ASSERT(x, ret) if (!(x)) return ret ///////////////////////////////////////////////////////////////// //// /// Read File // ////////////////////////////////////////////////////////////// LPVOID ReadFileBinaryEx(LPTSTR _ptcFileName, size_t _stFileSize, size_t _stBytesToRead, size_t _stExtension) { HANDLE hfile; PCHAR result; DWORD fread; RET_ASSERT((_ptcFileName != NULL) && (_stFileSize != NULL, NULL)); hfile = CreateFile(_ptcFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); RET_ASSERT(hfile != INVALID_HANDLE_VALUE, NULL); _stFileSize = (_stBytesToRead == NULL) ? GetFileSize(hfile, NULL) : _stBytesToRead; RET_ASSERT(_stFileSize != 0, NULL); if (_stExtension != NULL) _stFileSize += _stExtension; result = new char[_stFileSize]; if (!ReadFile(hfile, result, _stFileSize, &fread, NULL)) { delete [] result; result = NULL; } CloseHandle(hfile); return result; } LPVOID ReadFileBinary(LPTSTR _ptcFileName, size_t _stFileSize) { char * result; result = (char*)ReadFileBinaryEx(_ptcFileName, _stFileSize, NULL, NULL); RET_ASSERT(result != NULL, NULL); return result; } LPSTR ReadAllText(LPTSTR _ptcFileName) { PCHAR result; size_t length; result = (PCHAR)ReadFileBinaryEx(_ptcFileName, 0, NULL, 1); RET_ASSERT(result != NULL, NULL); *(result + length - 1) = '\0'; return result; } ///////////////////////////////////////////////////////////////// //// /// Write File // ////////////////////////////////////////////////////////////// DWORD WriteFileBinaryEx(LPTSTR _lpFileName, LPVOID _lpvData, size_t _stSize, INT _nCreationDisposition, DWORD _dwDesiredAccess) { HANDLE hfile; DWORD fwritten; RET_ASSERT(_lpvData != NULL && _lpFileName != NULL && _stSize != NULL, NULL); hfile = CreateFile( _lpFileName, _dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, _nCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); RET_ASSERT(hfile != NULL, NULL); RET_ASSERT(WriteFile(hfile, _lpvData, _stSize, &fwritten, NULL), NULL); CloseHandle(hfile); return fwritten; } void WriteAllText(LPTSTR _lpFileName, LPSTR _lpvData) { DWORD ret; RET_ASSERT(_lpvData != NULL,); ret = WriteFileBinaryEx(_lpFileName, _lpvData, strlen((PCHAR)_lpvData), GENERIC_WRITE, OPEN_ALWAYS); RET_ASSERT(ret != NULL,); } int AppendFile(LPTSTR _FileName, void * _Data, size_t _Size, BOOL _CreateIfNotExist) { HANDLE hfile; DWORD pos, written; RET_ASSERT((_FileName != NULL) && (_Data != NULL) && (_Size != 0), NULL); hfile = CreateFile(_FileName, FILE_APPEND_DATA, FILE_SHARE_WRITE, NULL, (_CreateIfNotExist)? OPEN_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); pos = SetFilePointer(hfile, 0, NULL, FILE_END); LockFile(hfile, pos, 0, _Size, 0); RET_ASSERT(WriteFile(hfile, _Data, _Size, &written, NULL), NULL); UnlockFile(hfile, pos, 0, _Size, 0); return (int)written; } int AppendFile(LPTSTR _FileName, LPSTR _String, BOOL _CreateIfNotExist) { return AppendFile(_FileName, _String, strlen(_String), _CreateIfNotExist); } int AppendFile(LPTSTR _FileName, LPSTR _String) { return AppendFile(_FileName, _String, strlen(_String), TRUE); } BOOL FileExist(LPTSTR _FileName) { return (GetFileAttributes(_FileName) == INVALID_FILE_ATTRIBUTES)? FALSE : TRUE; }
-
Joe_M. schrieb:
Mal in eine andere Richtung gedacht: Wie viele CPUs / Kerne sind im (Produktiv-) System vorhanden? Wenn es zwei oder mehr sind: Da es sowieso schon um mehrere Dateien geht, könnte man das Einlesen und Verarbeiten auf mehrere Threads verteilen?
Die Anzahl der möglichen Kerne auf denen die Anwendung ausgeführt wird sind 1-4. Daher eher schiwierig.
Aber man könnte das ganze mal durchdenken was währe wenn... (zB mit Systemabfrage)
Bei einem kern ist kein Vorteil zu erwarten.
Was ist bei mehreren Kernen, geht es schneller wenn die Anwendung aus mehreren Dateien ließt?