Entwickler-Ecke
Dateizugriff - Schnelles parsen von CSV-Dateien
OsCor - Mo 30.01.12 15:20
Moderiert von
Narses: Abgetrennt von [url=http://www.delphi-forum.de/viewtopic.php?p=657367#657367]hier[/url].
@zuma
Auf der Suche nach ganz was anderem habe ich diesen Thread überflogen. Dabei habe ich die mir noch unbekannten Methoden in Stringlist gesehen (Delimiter etc.). Da ich das Parsing von Strings, speziell von csv-Dateien (das ich sehr häufig brauche), bisher immer mit Pos, Copy und Delete erledigt habe (auch bei 800 000 Zeilen), hat mich der Hinweis darauf schier elektrisiert. Hast du Vergleiche bez. der Geschwindigkeit?
Oswald
P.S. Ich hoffe, dass mczero098 mir das Thread-Hijacking nicht verübelt.
jaenicke - Mo 30.01.12 18:37
OsCor hat folgendes geschrieben : |
Da ich das Parsing von Strings, speziell von csv-Dateien (das ich sehr häufig brauche), bisher immer mit Pos, Copy und Delete erledigt habe (auch bei 800 000 Zeilen), hat mich der Hinweis darauf schier elektrisiert. Hast du Vergleiche bez. der Geschwindigkeit? |
Wenn es schnell gehen soll, würde ich mit PChars arbeiten. Dann brauchst du nicht die ganzen Stringoperationen. Wenn die Daten entsprechend formatiert sind, könntest du einfach die CSV-Trennzeichen durch Nullzeichen ersetzen und Pointer auf die Zeilenbestandteile speichern. Das sollte sehr fix gehen.
zuma - Di 31.01.12 09:01
OsCor hat folgendes geschrieben : |
@zuma
Auf der Suche nach ganz was anderem habe ich diesen Thread überflogen. Dabei habe ich die mir noch unbekannten Methoden in Stringlist gesehen (Delimiter etc.). Da ich das Parsing von Strings, speziell von csv-Dateien (das ich sehr häufig brauche), bisher immer mit Pos, Copy und Delete erledigt habe (auch bei 800 000 Zeilen), hat mich der Hinweis darauf schier elektrisiert. Hast du Vergleiche bez. der Geschwindigkeit?
Oswald
P.S. Ich hoffe, dass mczero098 mir das „Thread-Hijacking” nicht verübelt. |
Hallo Oswald,
ich nutze das fast nur so für alle meine Import-Routinen.
Genaue Zeitmessungen hab ich nicht, aber wir haben eine alte Routine, die das auch mit Stringzerlegung per copy, pos, usw. macht, und die is nix schneller, lediglich aufwändiger zu schreiben.
Schreibe aktuell gerade einen csv-Import, werde mal mit gettickcount messen, wie lange so ein import dauert, und dann hier bescheid sagen.
Aber unabhängig von der Geschwindigkeit ist die Stringlist/Delimiter-variante wesentlich bequemer zu proggen ;)
Zuma
OsCor - Di 31.01.12 09:40
Auf die Zeiten bin ich gespannt.
@jaenicke
Meinst du mit „entsprechender Formatierung” feste Positionen innerhalb der Zeile? Wenn ja, dann fielen natürlich auch schon ohne Pointer 2 Arbeitsschritte weg. Da ich aber Endverwerter diverser Datenquellen bin, auf die ich nicht immer Einfluß habe, bleibt der „normale” csv-Input wohl Standard.
Aber vielleicht magst du noch erklären, wie du den Ansatz mit Pointer realisieren würdest? Ich sage es ehrlich: Wie viele andere habe ich die Arbeit damit bisher gescheut (hauptsächlich aus Angst vor schwer zu findenden Fehlern) - was nicht heißen muß, dass ich es nicht noch lerne :)
Oswald
jaenicke - Di 31.01.12 10:04
OsCor hat folgendes geschrieben : |
Meinst du mit entsprechender Formatierung feste Positionen innerhalb der Zeile? |
Nein, ich meine vor allem, ob escapte Zeichen vorkommen. Denn dann ist der String ja nicht im "Endzustand" drin, sondern muss bearbeitet werden.
Wenn man einfach nur die Positionen bestimmen muss, geht das sehr gut. Ein Beispiel kann ich erst heute Abend posten, da ich jetzt bei der Arbeit bin. Arbeiten tue ich auf diese Weise z.B. hier, nur bei dir lässt es sich noch weiter optimieren:
http://www.delphi-forum.de/viewtopic.php?p=567719
Mehr dazu heute Abend, da sollte ich kurz Zeit dafür haben. Als Maßstab: In dem verlinkten Programm lese ich eine 300 MiB .reg Datei in unter 5 Sekunden komplett zeichenweise in den Baum ein und parse diese. ;-)
zuma - Di 31.01.12 14:28
Hallo OsCor,
kann leider beim aktuellen Import keine Zeiten geben,
da mit Gettickcount nicht messbar ;)
Hab aktuell eine Datei mit 2 - n Werten pro Zeile (im Schnitt so 150 Werte)
die Zeile einer weiteren Stringlist als DelimitedText zuzuweisen,
die ersten 2 Werte in lokale Vars und den Rest in eine weitere Stringlist zu adden,
ergibt bei gettickcount keine Differenz, geht also zu schnell ums messen zu können.
Das Eintragen der Werte in die DB dauert da deutlich länger (ca. 0,016 Sec).
Ich warte auch mal auf Jaenicke, mal sehen, ob ich noch was dazulernen kann ;)
Evtl. darf ich die Tage noch einem Kollegen helfen, der muss eine Datei mit jeweils 5000 Messwerten einlesen, die mit der DB abgleichen, aus den Differenzen ein Diagramm erstellen und dieses als PDF ablegen, das ganze muss unter 5 Sec pro Durchlauf passieren, da werden wir wohl etwas genauer auf die Zeiten achten müssen ;)
Zuma
OsCor - Di 31.01.12 16:19
So zeitkritisch ist es bei mir nicht. Aber bei jetzigen Laufzeiten von 35 Sekunden bei einem Lesevorgang und knapp 900 000 Zeilen lohnt sich eine effektive Programmierung ganz sicher, zumal ich eigentlich auch noch Ergebnisse als Graphik darstellen wollte. Auswertung auf „Knopfdruck” stellen sich die Kollegen sicher anders vor :roll:
Oswald
jaenicke - Do 02.02.12 08:30
So, nur kurz in den letzten Minuten geklöppelt:
Kleine Datei mit einer halben Million Einträgen mit je 6 Spalten, die ersten 20 Einträge des Arrays werden ausgegeben. Einlesezeit bei mir 0,1 Sekunden. Mehr dazu später, hab keine Zeit.
zuma - Do 02.02.12 10:07
Hallo Sebastian,
danke fürs hochladen,
hab zwar keine Zeit im Moment,
mir das genauer anzugucken,
bzw. mal eine meiner Routinen umzubauen
und nen (Geschwindigkeits-) Vergleich zu machen
aber die wird sich schon noch finden ;)
Zuma
OsCor - Do 02.02.12 12:07
Danke, Sebastian! Ich werde auch erst heute abend daran kommen, aber bei der Aussicht auf schnelle Laufzeiten freue ich mich schon darauf.
Oswald
Horst_H - So 05.02.12 14:26
Hallo,
in 0.1 Sekunden 22,6 MB ist ja schon enorm schnell, schneller als meine Festplatte, also aus dem Cache heraus.
Bei mir dauerte das gecachte Einlesen in eine popelige Stringlist mit freepascal schon 0,2 Sekunden.
Insgesamt mit dem nacheinander zerlegen aller 507075 Zeilen in 1 Sekunde ( 3,2 Ghz Phenom II, mit ausgeschaltetem Cool&quite)
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62:
| program unbekannt;
uses Classes, SysUtils; var t0,t1 :TDateTime; i,k,sp: integer; sExec: AnsiString; AStringList, BStringList, CStringlist: TStringList; begin T1:= now; T0:= now; T0 := 2*T0-T1; AStringList:= TStringlist.Create;
sExec := IncludeTrailingPathDelimiter(GetCurrentDir)+'Mappe1.csv';
AStringList.loadfromfile(sExec); T1 := now; Writeln('Es sind ',AStringlist.Count,' Zeilen'); Writeln('Einlesezeit ',FormatDateTime('HH:NN:SS.ZZZ',T1-T0));
BStringList:= TStringlist.Create; BStringlist.delimiter := ';'; CStringList:= TStringlist.Create; k := 0; For i := 0 to AStringlist.Count-1 do begin BStringList.delimitedtext := AStringList[i]; CStringList.AddStrings(BStringList); sp := 0; While k < CStringlIst.count do begin CStringList.Objects[k] := TObject(sp shl 24+i); inc(k); inc(sp); end; end; T1 := now; AStringList.free; writeln(bStringList.text); BStringList.free;
IF k >= 40 then k:= 39 else k := CStringlist.count-1; For i := 0 to k do writeln(cStringlist[i],' Zeile ',Integer(CStringList.Objects[i]) AND $00FFFFFF:8,' Spalte ',Integer(CStringList.Objects[i]) shr 24:5) ; Writeln('Gesamtzeit: ',FormatDateTime('HH:NN:SS.ZZZ',T1-T0)); CStringlist.free; readln; end. |
Ausgabe
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
| Free Pascal Compiler version 2.6.0 [2011/12/25] for i386 .... Es sind 507075 Zeilen Einlesezeit 00:00:00.193 639506 assasad 639506 assasad 639506 assasad
132432 Zeile 0 Spalte 0 assasad Zeile 0 Spalte 1 132432 Zeile 0 Spalte 2 assasad Zeile 0 Spalte 3 132432 Zeile 0 Spalte 4 assasad Zeile 0 Spalte 5 132433 Zeile 1 Spalte 0 assasad Zeile 1 Spalte 1 132433 Zeile 1 Spalte 2 assasad Zeile 1 Spalte 3 132433 Zeile 1 Spalte 4 assasad Zeile 1 Spalte 5 132434 Zeile 2 Spalte 0 assasad Zeile 2 Spalte 1 .... 132437 Zeile 5 Spalte 0 assasad Zeile 5 Spalte 1 132437 Zeile 5 Spalte 2 assasad Zeile 5 Spalte 3 132437 Zeile 5 Spalte 4 assasad Zeile 5 Spalte 5 132438 Zeile 6 Spalte 0 assasad Zeile 6 Spalte 1 132438 Zeile 6 Spalte 2 assasad Zeile 6 Spalte 3 Gesamtzeit: 00:00:01.025 |
Ich gehe zunächst davon aus, dass das Einlesen nicht "die" Bremse ist.
Gruß Horst
Edit:
Ich habe jetzt auch alle Daten im Speicher mit Angabe von Zeile und Spalte ( maximal 255) .
Natürlich ist die Variante von jaenicke wesentlich speichersparender, aber selbst 900000 Zeilen lassen wohl meist im Hauptspeicher halten.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!