![]() |
Re: TStringlist mit 60000 Einträgen zu langsam
Zitat:
Und wenn er die Liste nur durcharbeitet ... einen Eintrag nach dem anderem ... da braucht er keine Hashlisten, wobei hier eine Hashliste sogar einen Hauch langsamer wäre. Irgendwann müssen die Hashs berechnet werden, welche dann nichtmal nötig wären, wenn man eh nicht sucht. |
Re: TStringlist mit 60000 Einträgen zu langsam
Na Ihr spökenkieker,
Laßt Friedemann doch erst einmal erzählen was er da treibt, ich spekuliere auf etwas csv-ähnliches. Gruß K-H |
Re: TStringlist mit 60000 Einträgen zu langsam
Zitat:
Ich denke, man kann die einzelnen Bestandteile in maximal 300ms (3x Copy, 2x Pos + overhead) auseinanderfriemeln. Woher kommt bloß das Märchen, das eine TStringlist ein Performancekiller ist? Es handelt sich um ein dynamisches Array von (String,Objekt)-Tupeln. Die dynamische Vergrößerung erfolgt nach einer sehr schnellen Heuristik. Natürlich kann man das schneller machen, und sollte es vielleicht sogar. Aber hier geht es erstmal um Grundlegendes. |
Re: TStringlist mit 60000 Einträgen zu langsam
Just for Info
Es gibt viele Klassen, die von TStrings abgeleitet sind. z.B. TStringList, TMemoStrings, TComboBoxStrings, TListBoxStrings, ... Der Knackpunkt ist, dass z.B. TMemoStrings ungefähr um den Faktor 50 langsamer ist als TStringList. Wenn man also mit einem TMemo arbeitet und das Property Lines benützt, ist das um Welten langsamer, als wenn man TStringList direkt benützt. |
Re: TStringlist mit 60000 Einträgen zu langsam
Abend zusammen,
zuerst mal vielen vielen Dank für die tollen Antworten. Ich versuche mal drauf zu reagieren: 1) FastMM - habe ich jetzt eine viertel Stunde gegoogelt und leider keine brauchbare Info bekommen, die mir erläutert, was ich damit konkret anfangen könnte. Hat jemand da zufällig einen geeigneten Thread im Kopf? 2) Bisschen Code. Ich bin ein fürchtlich schlechter Coder, daher verzicht ich am Anfang immer auf Code senden. Aber ich sehe ein, dass ich anders nicht voran käme. Zuvor kurz die Info: Die Textdateien bestehen zuerst aus langen Listen, z.B. ("[tab]" = Tabulator): Die [tab] ART [tab] D Häuser [tab] NN [tab] Haus usw. Diese Liste wird in eine Stringlist1 geladen, dann String für String durchgegangen, in Abhängigkeit von bestimmten Infos entweder die erste, zweite oder dritte Stelle ermittelt und die dann in eine neue Stringlist2 kopiert. Sind alle Strings von Stringlist1 abgearbeitet, werden die einzelnen Strings aus Stringlist2 noch direkt aneinander gefügt (wieder zu einem Fließtext). Letzteres habe ich direkt versucht, aber leider nicht hinbekommen; das zieht aber auch kaum Zeit; die meiste Zeit geht drauf fürs Durchgehen von Stringlist1. Hier ein Codeausschnitt aus einem Programmteil, der nicht mehrere Dateien, sondern nur einen Ausschnitt aus einer Datei verarbeitet ("Preview"-funktion); das Prinzip müsste daraus aber klar werden:
Delphi-Quellcode:
// Einzelne Texte zusammensetzen
begin quelle:= tstringlist.create; ziel:= tstringlist.create; try //Previewdatei laden quelle.LoadFromFile(extractfilepath(application.exename) + 'preview2.dat'); //Ersetzen von zwei Zeichen, da sich ansonsten im weiteren Analyseverlauf nicht korrekt verarbeitet werden; umständlich, aber anders weiß ichs nich.. quelle.Text:=stringreplace2(quelle.text, '"', 'ANFUEEEE'); quelle.Text:=stringreplace2(quelle.text, #39, 'EINFANFUEEEE'); for ii:=0 to quelle.Count-1 do begin wortarttemp:= gibmirwortart(quelle.strings[ii], #9); //Token zusammennehmen if pos('#' + wortarttemp + '#', tok)<>0 then //Bedingung; braucht keine Zeit, da der zu durchsuchende String tok nur ~40 Zeichen groß ist ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); //Lemma zusammennehmen if pos('#' + wortarttemp + '#', lem)<>0 then //s.o. begin lemmareal:= gibmirlemma(quelle.strings[ii], #9); schon:= 0; if (lemmareal= '<UNKNOWN>') and (checkbox2.checked) then //weitere Bedingungen begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); schon:= 1; end; if (lemmareal= '@card@') and (checkbox4.checked) then begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); schon:= 1; end; if (lemmareal= 'CARD') and (checkbox4.checked) then begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); schon:= 1; end; if (lemmareal= '@ord@') and (checkbox4.checked) then begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); schon:= 1; end; if schon=0 then ziel.text:= ziel.text + lemmareal; end; //Wortart zusammennehmen if pos('#' + wortarttemp + '#', poss)<>0 then //s.o. ziel.Text:= ziel.text + gibmirwortart(quelle.strings[ii], #9); end; //Wenn die Stringlist quelle durchgearbeitet ist und aller relevanten Strings in ziel, dann sollen die Strings in ziel zu einem fortlaufenden Text (-> zielende: string;) zusammengesetzt werden for x:=0 to ziel.Count-1 do zielende:= zielende + ' ' + ziel.Strings[x]; //Vorherige Ersetzungen rückgängig machen zielende:= stringreplace2(zielende, 'ANFUEEEE', '"'); zielende:= stringreplace2(zielende, 'EINFANFUEEEE', #39); //Ergebnis (Preview) in Memo ausgeben memo2.text:= zielende; finally quelle.free; ziel.Free; end; Der vorangegangene Code verweist auf folgende Routinen, die ich in Anlehnung an Funktionen ausm Netz verwende (hier hängt wohl die meiste Zeit..):
Delphi-Quellcode:
//Funktion für die Ersetzung; ist schneller als die alte stringreplace function stringreplace2(aString, FromStr, ToStr: AnsiString): AnsiString; var I: Integer; begin // check whether string are equal if FromStr = ToStr then begin Result := aString; Exit; end; Result := ''; // find fromstr I := Pos(FromStr, aString); while I > 0 do begin // copy all characters prior fromstr if I > 1 then Result := Result + Copy(aString, 1, I - 1); // append tostr Result := Result + ToStr; // delete all until after fromstr Delete(aString, 1, I + Length(FromStr) - 1); // find next fromstr I := Pos(FromStr, aString); end; Result := Result + aString; end; //hier wird die zweite Stelle des durch tab geteilten Strings aus quelle ermittelt function gibmirwortart(s:string; sep:char) :string; var t: Tstringlist; begin //hier muss jetzt das zweite Wort rausgefiltert werden t:= tstringlist.create; try extractstrings([char(sep)], [' '], pchar(s), t); result:= t.Strings[1]; finally t.free; end; end; //hier wird die erste Stelle des durch tab geteilten Strings aus quelle ermittelt function gibmirToken(s:string; sep:char) :string; var t: Tstringlist; begin //hier muss jetzt das zweite Wort rausgefiltert werden t:= tstringlist.create; try extractstrings([char(sep)], [' '], pchar(s), t); result:= t.Strings[0]; finally t.free; end; end; //hier wird die dritte Stelle des durch tab geteilten Strings aus quelle ermittelt function gibmirLemma(s:string; sep:char) :string; var t: Tstringlist; begin //hier muss jetzt das zweite Wort rausgefiltert werden t:= tstringlist.create; try extractstrings([char(sep)], [' '], pchar(s), t); result:= t.Strings[2]; finally t.free; end; end; 3. Hashs u.ä. kann ich leider nicht umsetzen, dafür reicht mein Anfängerwissen nicht.. Vielleicht habt ihr auf meiner Codebasis eine Idee zum Zeitsparen? Danke für Eure Mühe und viele Grüße zum Abend, Friedemann |
Re: TStringlist mit 60000 Einträgen zu langsam
FastMM gibs hier:
![]() binde es einfach mal ins projekt ein und schau obs schneller geht. und schau dir mal die faststrings.pas an: ![]() |
Re: TStringlist mit 60000 Einträgen zu langsam
Delphi-Quellcode:
Versuch es erstmal hiermit.
//hier wird die erste Stelle des durch tab geteilten Strings aus quelle ermittelt
function gibmirToken(s:string; sep:char) :string; begin Result := Copy(s, 1, Pos(#9, s) - 1); end; //hier wird die zweite Stelle des durch tab geteilten Strings aus quelle ermittelt function gibmirwortart(s:string; sep:char) :string; begin Delete(Result, 1, Pos(#9, s)); Result := Copy(Result, 1, Pos(#9, s) - 1); end; //hier wird die dritte Stelle des durch tab geteilten Strings aus quelle ermittelt function gibmirLemma(s:string; sep:char) :string; begin Delete(Result, 1, Pos(#9, s)); Delete(Result, 1, Pos(#9, s)); end; Mit Hilfe von PosEx ließe sich auch dieses noch weiter optimieren. Statt diesem "schon" und dem Ausführen von Abfragen, welche eh nie zuschlagen werden, da vorher schon eine Abfrage erfolgreich war, solltest du dich eventuell mal mit dem ELSE beschäftigen.
Delphi-Quellcode:
// Einzelne Texte zusammensetzen
begin quelle:= tstringlist.create; ziel:= tstringlist.create; try //Previewdatei laden quelle.LoadFromFile(extractfilepath(application.exename) + 'preview2.dat'); //Ersetzen von zwei Zeichen, da sich ansonsten im weiteren Analyseverlauf nicht korrekt verarbeitet werden; umständlich, aber anders weiß ichs nich.. quelle.Text:=stringreplace2(quelle.text, '"', 'ANFUEEEE'); quelle.Text:=stringreplace2(quelle.text, #39, 'EINFANFUEEEE'); for ii:=0 to quelle.Count-1 do begin wortarttemp:= gibmirwortart(quelle.strings[ii], #9); //Token zusammennehmen if pos('#' + wortarttemp + '#', tok)<>0 then //Bedingung; braucht keine Zeit, da der zu durchsuchende String tok nur ~40 Zeichen groß ist ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); //Lemma zusammennehmen if pos('#' + wortarttemp + '#', lem)<>0 then //s.o. begin lemmareal:= gibmirlemma(quelle.strings[ii], #9); if (lemmareal= '<UNKNOWN>') and (checkbox2.checked) then //weitere Bedingungen begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); end else if (lemmareal= '@card@') and (checkbox4.checked) then begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); end else if (lemmareal= 'CARD') and (checkbox4.checked) then begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); end else if (lemmareal= '@ord@') and (checkbox4.checked) then begin ziel.text:= ziel.text + gibmirtoken(quelle.strings[ii], #9); end; else ziel.text:= ziel.text + lemmareal; end; //Wortart zusammennehmen if pos('#' + wortarttemp + '#', poss)<>0 then //s.o. ziel.Text:= ziel.text + gibmirwortart(quelle.strings[ii], #9); end; //Wenn die Stringlist quelle durchgearbeitet ist und aller relevanten Strings in ziel, dann sollen die Strings in ziel zu einem fortlaufenden Text (-> zielende: string;) zusammengesetzt werden for x:=0 to ziel.Count-1 do zielende:= zielende + ' ' + ziel.Strings[x]; //Vorherige Ersetzungen rückgängig machen zielende:= stringreplace2(zielende, 'ANFUEEEE', '"'); zielende:= stringreplace2(zielende, 'EINFANFUEEEE', #39); //Ergebnis (Preview) in Memo ausgeben memo2.text:= zielende; finally quelle.free; ziel.Free; end; |
Re: TStringlist mit 60000 Einträgen zu langsam
Hallo friedemann,
ich kenne deine Eingabedaten nicht, aber ich vermute, dass die meiste Zeit nicht für das Lesen der Quelldaten, sondern für den Zusammenbau der Ergebnisdaten benötigt wird. Vergleiche einmal die beiden folgenden Code-Blöcke hinsichtlich ihrer Laufzeit:
Delphi-Quellcode:
Bei jedem Lesezugriff auf die Eigenschaft Text einer Stringliste werden alle Strings der Liste verkettet, damit das Ergebnis geliefert werden kann. Bei einem Schreibzugriff auf die Eigenschaft muss der zugewiesene String wieder in die einzelnen Zeilen zerlegt werden. Die Verwendung eines Hilfsstrings führt hier zu einer spürbaren Verkürzung der Laufzeit.
var
T1, T2: Cardinal; SL: TStrings; i: Integer; s: string; begin // Test 1 SL := TStringList.Create; try T1 := GetTickCount; for i := 1 to 20000 do SL.Text := SL.Text + 'Testzeile'; T2 := GetTickCount; ShowMessage (Format('%d Zeilen, %d msec', [SL.Count, T2 - T1])); finally SL.Free; end; // Test 2 SL := TStringList.Create; try T1 := GetTickCount; s := ''; for i := 1 to 20000 do s := s + 'Testzeile' + sLineBreak; SL.Text := s; T2 := GetTickCount; ShowMessage (Format('%d Zeilen, %d msec', [SL.Count, T2 - T1])); finally SL.Free; end; end; Warum nimmst du eigentlich den Umweg über eine Stringliste? Am Ende fügst du ja doch wieder alles zu einem einzelnen String zusammen. Gruß Hawkeye |
Re: TStringlist mit 60000 Einträgen zu langsam
Morgen zusammen,
also ein bisschen hat es was gebracht, aber nicht viel.. Bisschen musste ich anpassen, aber sonst einleuchtend schneller:
Delphi-Quellcode:
Ich versuche jetzt noch das mit FastMM.
function gibmirToken(s:string; sep:char) :string;
begin Result := Copy(s, 1, Pos(#9, s) - 1); end; //hier wird die zweite Stelle des durch tab geteilten Strings aus quelle ermittelt function gibmirwortart(s:string; sep:char) :string; begin Delete(s, 1, Pos(#9, s)); Result := Copy(s, 1, Pos(#9, s) - 1); end; //hier wird die dritte Stelle des durch tab geteilten Strings aus quelle ermittelt function gibmirLemma(s:string; sep:char) :string; begin Delete(s, 1, Pos(#9, s)); Delete(s, 1, Pos(#9, s)); result:= s; end; Danke allen für die Tipps! Grüße, friedemann |
Re: TStringlist mit 60000 Einträgen zu langsam
Zitat:
Delphi-Quellcode:
einfach nur
ziel.Text := ziel.Text + ...;
Delphi-Quellcode:
machen können.
ziel.Add(...);
Beim Auslesen dann
Delphi-Quellcode:
PS:
ziel.LineBreak := ' ';
zielende := ziel.Text;
Delphi-Quellcode:
Diese hatte doch eh nichts gebracht.
for x:=0 to ziel.Count-1 do
zielende:= zielende + ' ' + ziel.Strings[x]; Da "ziel" nur aus EINEM langen String besteht oder hab ich irgendwie übersehn, wo mehrere Strings/Zeilen erzeugt werden? Da es aber .LineBreak bei dir noch nicht geben wird,
Delphi-Quellcode:
PS: Da StringRepleace in Delphi nicht grade optimal arbeitet, wäre es besser, wenn du diese mit in die Schleife reinmachst, anstatt es über den GROßEN String zu jagen.
ziel.Add(...);
// und am Ende dann dein for x:=0 to ziel.Count-1 do zielende:= zielende + ' ' + ziel.Strings[x];
Delphi-Quellcode:
for ii:=0 to quelle.Count-1 do
begin ... ziel.Add({die Wörter}); ... end; for x:=0 to ziel.Count-1 do zielende:= zielende + ' ' + stringreplace2(stringreplace2( ziel.Strings[x], 'ANFUEEEE', '"'), 'EINFANFUEEEE', #39); memo2.text := zielende; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:38 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz