C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige
-
Du hast da noch einen Denkfehler (der vermutlich keine große Rolle spielt): Du liest zeilenweise in einen 300 Byte großen Block ein, das heisst aber nicht, dass der Rest der Zeile ignoriert wird, wenn sie länger als 300 Zeichen ist. Der Rest der Zeile wird dann in folgenden
fgets
Aufrufen eingelesen, da wird nichts übersprungen.
Beispiel
Die Zeile ist 752 Zeichen lang und sie wird in 300 Byte Blöcken gelesen. Das resultiert in 3 Lesenoperationen:- Bytes 0-299 werden gelesen, Abbruch, weil die Puffergröße 300 Bytes sind, aber noch kein Zeilenende gelesen wurde
- Bytes 300-599 werden gelesen, Abbruch, weil die Puffergröße 300 Bytes sind, aber noch kein Zeilenende gelesen wurde
- Bytes 600-751 werden gelesen, Abbruch, weil
fgets
auf ein Zeilenende stößt
Edit: Der folgende Absatz stimmt nicht
Das Ganze birgt eine Gefahr: Der Puffer muss nicht nullterminiert sein, weil sein Inhalt vom Dateiinhalt abhängt und deinfgets
Aufruf den ganzen Puffer beschreibt, inklusive des letzten Zeichens. Zur Sicherheit solltest du den Puffer um ein Byte vergrößern und das mit0
besetzen, damit ist immer sichergestellt, dass der Puffer nullterminiert ist und gefahrlos mit denstr...
Funktionen benutzt werden kann. Also zB Puffergröße 301 Byte, das letzte Element mit 0 besetzen und max. 300 Byte lesen. Oder du benutzt diestrn...
Funktionen, die wiederum ihre Eigenheiten haben.Zum anderen ein paar Stilmängel (obwohl ich mit bei C da nicht sicher bin):
Versuche Variablen so lokal wie möglich und so lokal wie möglich zu benutzen. Und sie sollten, falls möglich, schon bei der Definition initialisiert werden:
Stattint main() { FILE* f; // Definition ohne Initialisierung, Wert zufällig // diverses Zeugs f = fopen(...); }
besser
int main() { // diverses Zeugs FILE* f = fopen(...); // sofort initialisiert }
Und eine weitere Überlegung:
Wenn die Datei häufig durchsucht werden soll, ist es vielleicht sinnvoll, eine Indexdatei zu erstellen, die die Dateioffsets jedes Zeilenbeginns enthält. Die wäre, grob überschlagen, etwa 880MB groß (80GB/752B sind etwa 110M Zeilen, pro Zeile ein 64bit Dateioffset). Dann könntest du die Indexdatei binär lesen, direkt an den Zeilenanfang jeder Zeile in der Datendatei springen und nur so viele Zeichen jeder Zeile lesen, wie tatsächlich für die Suche gebraucht werden. @Quiche-Lorraine hat das ja schon erwähntWas @DirkB vermutlich meint, ist dass aktuelle CPUs Fließkommazahlen als 8 Byte
double
in einem CPU Register unterbringen. Einfloat
ist nur 4 Byte groß, um damit zu arbeiten muss die CPU die Fließkommazahl erst vondouble
nachfloat
konvertieren. Dürfte in deinem Fall aber wohl keine Rolle spielen, da File I/O um Größenordnungen langsamer sind als die Konvertierung.
-
@jasmin89 sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
@wob: Gibt fgets auch die gelesene Länge zurück?
Ach mist, tut es nicht.
Oder meinst du strln zu verwenden und mit einer int Variable diese dann immer zu addieren
Also: z.b long int length = strlen(puffer)+length;Ich wollte den von mir erträumten Längen-Rückgabewert addieren. Sorry!
Die 77273022464 sind die Dateigröße.
Die solltest du einmalig am Anfang automatisch ermitteln (siehe https://www.c-plusplus.net/forum/post/2617554)
Ich verwende die Vergleichsfunktion SGA_stricmp da ich nicht alles in Großbuchstaben eingeben möchte.
schon klar, aber warum
tolower
UNDtoupper
nacheinander? Wie unterscheidet sich das davon, wenn du alles nur in Kleinbuchstaben umwandeln würdest, ohne es vorher in Großbuchstaben gewandelt zu haben? Wenn du AbC suchst und daraus erst ABC machst, um dann abc daraus zu machen, wozu ist dann der Zwischenschritt mittoupper
gut?
-
@DocShoe sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Das Ganze birgt eine Gefahr: Der Puffer muss nicht nullterminiert sein, weil sein Inhalt vom Dateiinhalt abhängt und dein fgets Aufruf den ganzen Puffer beschreibt, inklusive des letzten Zeichens. Zur Sicherheit solltest du den Puffer um ein Byte vergrößern und das mit 0 besetzen, damit ist immer sichergestellt, dass der Puffer nullterminiert ist
Siehe https://en.cppreference.com/w/c/io/fgets:
Reads at most count - 1 characters from the given file stream [...] If bytes are read and no errors occur, writes a null character at the position immediately after the last character written to str.
Das ist also sichergestellt, ohne dass du selbst mit -1 rumhantieren müsstest oder den Puffer vergrößern müsstest.
-
@DocShoe sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
@Th69
Weiss nicht... hängt davon ab, wie lange der Vorgang dauert, wenn ich auf einen prozentualen Fortschritt schaue und da passiert längere Zeit nix, dann vermute ich, dass da iwas hängt. Prozentangaben mit Nachkommastellen finde ich da durchaus ok. Mann kann natürlich auch noch die Dauer anzeigen, die seit Start verstrichen ist, dann wäre ich auch mit ganzzahligen Prozentangaben glücklich.Du hast mich da mißverstanden. Bei einer Anzeige mit 2 Nachkommastellen würde man eben in Deziprozentwerten rechnen (so wie man Geldwerte auch in Cent oder sogar noch kleinere Werte - z.B. für Bankensysteme - berechnet).
-
@wob sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Siehe https://en.cppreference.com/w/c/io/fgets:
Reads at most count - 1 characters from the given file stream [...] If bytes are read and no errors occur, writes a null character at the position immediately after the last character written to str.
Das ist also sichergestellt, ohne dass du selbst mit -1 rumhantieren müsstest oder den Puffer vergrößern müsstest.
Sieh man einer an, man lernt nicht aus
-
@Th69 sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Du hast mich da mißverstanden. Bei einer Anzeige mit 2 Nachkommastellen würde man eben in Deziprozentwerten rechnen (so wie man Geldwerte auch in Cent oder sogar noch kleinere Werte - z.B. für Bankensysteme - berechnet).
Aber warum "würde man [...] in Deziprozentwerten rechnen", was soll da der Vorteil sein? Verstehe ich nicht. Ich sehe da kein Problem mit einer float-Division. Du willst dem Anwender ja trotzdem 17,54% anzeigen und nicht 1754. Du müsstst dich also auch noch selbst darum kümmern, da ein Dezimaltrenner einzublenden. (das ist ja gerade nicht wie bei Geld, wo es fixe Minimaleinheiten wie eben z.B. Cent gibt).
-
Es ging ja darum, die Prozentzahl nur dann anzuzeigen, wenn sie sich geändert hat (und nicht bei jedem Schleifendurchlauf):
if (pct != current_pct) { current_pct = pct; // output }
Und bei Fließkommazahlen wäre dieser Vergleich bei jedem Schleifendurchlauf immer positiv.
Aber wo ich jetzt genauer darüber nachdenke, müßtefabs(pct - current_pct) >= 0.01
(bei 2 Nachkommastellen) als Bedingung genügen.Würde mich trotzdem interessieren, ob die Umwandlung in Fließkommazahlen und deren Ausgabe nicht langsamer ist (bei so großen GB-Dateien), als die Verwendung einer optimierten Fixkommazahl-Ausgabe.
-
@jasmin89 sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
@DirkB: Wieso hat denn float Nachteile gegenüber double, bezüglich Konvertierung. Verstehe nicht genau was du mit konvertierung meinst. Wenn ich nur float verwende, wird da was konvertiert?
Gerechnet wird bei Fließkommaoperationen in double.
printf
nimmt bei Fließkommatypen auch mindestens doubleAlso Berechnung in double, Konvertierung nach float und wieder nach double für printf.
float hat nur bei sehr großen Arrays Vorteile gegenüber double.
-
Damit hier kein falscher Eindruck bei @jasmin89 aufkommt: Diese Rechentricks sparen höchstens ein oder zwei CPU-Takte, wenn überhaupt so viel. Takte, die völlig irrelevant sind, weil dein Programm die meiste Zeit entweder auf die Daten aus der Datei wartet, oder (bevor du dein Problem behoben hattest) darauf, dass deine Prozentausgabe auf der Konsole landet. Beides Operation, die um Größenordnungen länger dauern als ein paar float/double Berechnungen auf Teilen der CPU, die wahrscheinlich sowieso nichts besseres zu tun haben.
-
@Th69 sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
if (pct != current_pct)
Du kannst ja auch schreiben
if (pct >= current_pct + 0.01)
- außerdem hat man vielleicht noch eine "ETA hat sich um mindestens 10 Sekunden geändert"-Bedingung.Wenn du nutzlose Mikro-Optimierungen willst, kannst du auch
next_update_pct = pct + 0.01;
undif (pct >= next_update_pct)...
schreiben.
-
Danke für die Hilfe. Ich konnte einige Tipps umsetzen.
@DocShoe: Danke für den Tipp. Habe den Debugger angeworfen und tatsächlich es werden immer 300 Byte Blöcke eingelesen. Das war mir so nicht bewusst. Dann wäre eine Lösung eine solche Indexdatei zu erstellen, damit ich nur z.b 300 Byte einlesen muss. Habe ich das richtig verstanden? Sonst eine andere Möglichkeit nur 300 Byte von einer Zeile einzulesen habe ich nicht? Ich habe probiert das Array um 1 Byte größer zu machen und das Array mit '\0' zu füllen. Aber es wird immer die gesamte Zeile, jeweils 300 Byte, eingelesen bis \n.
@wob: Du hast recht tolower UND toupper nacheinander macht nicht viel Sinn. Ich habe das ausgebessert.
-
Ist das mit den 300 Zeichen eine Anforderung (d.h. du willst nur die Treffer aus den ersten 300 Zeichen, und es könnte auch Treffer nach 300 geben, die du ausschließen möchtest), oder ist das eine voreilige Optimierung (d.h. du weißt es kommt nur in den ersten 300 Zeichen vor und denkst, das wäre irgendwie effizienter, wenn du das so machst)?
-
Das ist eine voreilige Optimierung, das was ich Suchen möchte kommt nur in den ersten 300 Zeichen vor. Ich dachte halt dass die Suche dann schneller wäre wenn ich nicht die Gesamte Zeile einlesen muss.
-
@jasmin89 sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Das ist eine voreilige Optimierung, das was ich Suchen möchte kommt nur in den ersten 300 Zeichen vor. Ich dachte halt dass die Suche dann schneller wäre wenn ich nicht die Gesamte Zeile einlesen muss.
Dann spar dir das einfach. Du musst schließlich den Rest der Zeile auch einlesen, um das Ende der Zeile zu finden. Dateien sind bloß eine Folge von Zeichen, Zeilen haben keinerlei technische Bedeutung. Zeilen sind bloß eine Interpretation eines bestimmten Zeichens, da steckt aber nicht mehr dahinter, als dass das Zeichen Nummer 65 als 'A' interpretiert wird.
Man kann zwar mutmaßen, ob sich vielleicht Optimierungen dadurch ergeben, dass man sich eventuell ein paar Suchoperationen sparen kann, aber oft ist einfacher auch schneller. Mein Tipp daher, was du ausprobieren solltest: Datei blockweise lesen (gerne auch große Blöcke, die kleinste logische Einheit alter Festplatten ist 4kB, heutzutage bekommt man aber auch leicht 512kB in einem Rutsch) und die Blöcke durchsuchen. Aufpassen an den Rändern, dass man nichts verpasst, wenn der Suchbegriff genau auf einer Kante liegt. Beim Lesen möglichst stumpf und unformatiert lesen, also
fread
.Was willst du am Ende haben? Gegebenenfalls könnte es dadurch schwieriger werden. Die Anzahl der Treffer ist einfach. Den Treffer selbst? Auch trivial. Die Zeilennummer der Treffer? Dann müsste man noch mitzählen, an wie vielen Zeilentrennzeichen man vorbei gekommen ist, auch einfach. Wenn du aber beispielsweise die ganze Zeile zu einem Treffer haben möchtest, wird es leider doch etwas kompliziert, weil dann das blockweise Lesen nicht mehr so einfach ist.
-
Das Blockweise lesen klingt interessant. Aber habe ich da wirklich auch eine schnellere Suche? Denn ich muss ja trotzdem immer den gesamten Block durchsuchen. Wenn nichts gefunden wurde wird der Nachfolgende Block durchsucht. Als Leihe würde ich vermuten dass dies gleichschnell ist als alle Zeilen einzulesen. Ich bin keine Informatikerin, daher kann ich auch falsch liegen.
@SeppJ sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Was willst du am Ende haben?
Am Ende möchte ich einen Eindeutigen Ticker haben mit dem ich einen bestimmten Datensatz zuordnen kann. Ich Suche z.b "great falls". In der 80 GB Datei gibt es mehrere Datensätze die "great falls" beinhalten. Ich als Mensch kann die gefundenen Einträge zu "great falls" richtig interpretieren. Während der Suche merke ich z.b dass "great falls tt" der richtige Datensatz ist. Der Ticker dazu ist z.b. 10584. Ich speichere die gefundene Zeile in der Variable Position, setze den File pointer f auf den Anfang der Datei, springe mit SEEK_CUR zu der gefundenen Zeile und printe die Daten in eine .csv Datei (also die den Ticker 10584 haben). Dann habe ich in der .csv Datei alle Daten mit dem Ticker 10584. Ist etwas kompliziert aber ich habe keine bessere Möglichkeit gefunden. Das ist eben nur ein Teil vom Code.
-
Was genau meinst du mit dem Ticker? Das
position = ftello64(f);
aus deinem Post ganz oben?Wenn das um einen echten, produktiven Einsatzzweck statt den Lerneffekt geht: Ich habe den Eindruck, du programmierst
grep
nach. Du wirst alleine niemals ein Programm schreiben, das besser alsgrep
dein Problem löst.
-
@SeppJ sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Was genau meinst du mit dem Ticker?
Ticker im Sinne von Tickersymbol, d.h. eindeutige ID?
-
@wob sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
@SeppJ sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Was genau meinst du mit dem Ticker?
Ticker im Sinne von Tickersymbol, d.h. eindeutige ID?
Ja genau
-
@wob Ich kenne grep nicht und weiß auch nicht was das macht. Ich muss mir das mal anschauen. Ticker: Damit kann ich einen Datensatz zuordnen, dieser ist eindeutig.
Als Beispiel der Datensatz great falls tt hat den Ticker: 10584. Dieser Datensatz enthält 1500 Zeilen. Der Datensatz "great falls rr" mit dem Ticker 19510 interessiert mich nicht und diesen brauche ich nicht. Ich gebe also im Programm "great falls" ein und nach einiger Zeit erhalte ich folgende Ausgabe:great falls rr|Ticker15850 -> Enthält 2501 Zeilen an Daten
great falls ee|Ticker19510 -> Enthält 8500 Zeilen an Daten
great falls tt|Ticker10584 -> Enthält 1500 Zeilen an Daten
great falls uu|Ticker28545 -> Enthält 350 Zeilen an Datenposition = ftello64(f) hier speichere ich die Position des File pointers ab. Wenn ich z.b diese 4 Suchergebnisse habe, weiß ich nun dass ich nur das Ergebnis vom Ticker 10584 benötige. Ich breche dann die Suche ab. Um nun diese Daten zu exportieren, müsste ich den File Pointer wieder an den Anfang der Datei setzen, um genau nach diesen Ticker zu suchen. Damit ich das nicht machen muss, springe ich an diese Gespeicherte position = ftello64(f). Das ist genau die Zeile in der der Ticker 10584 beginnt. Dann kann ich sofort diese Daten exportieren. Das funktioniert auch super.
-
@wob sagte in C programm, große .txt Datei durchsuchen, mit Prozent Fortschrittsanzeige:
Wer ist "er"? OP heißt @jasmin89, da würde ich per Default "sie" annehmen.
Sorry, hatte nicht auf den Namen geachtet.