Delphi-PRAXiS
Seite 2 von 5     12 34     Letzte »    

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   TStringlist mit 60000 Einträgen zu langsam (https://www.delphipraxis.net/147517-tstringlist-mit-60000-eintraegen-zu-langsam.html)

himitsu 10. Feb 2010 14:31

Re: TStringlist mit 60000 Einträgen zu langsam
 
Zitat:

Zitat von hoika
Die 1 GB RAM könnten schon das Problem sein,
wenn Delphi auch noch offen ist.

So hungrig war Delphi 5 noch nicht.

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.

p80286 10. Feb 2010 15:28

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

alzaimar 10. Feb 2010 16:23

Re: TStringlist mit 60000 Einträgen zu langsam
 
Zitat:

Zitat von Tyrael Y.
...da Stringoperationen nicht gerade sehr schnell sind...würde ich sagen, daß das Benutzen der StringListe selbt auch schon ein Fehler ist...

Wieso? Ich habe 100.000 Strings in einer Stringlist. Jedes Element mit 'Copy' zu bearbeiten, dauert 30ms, jeweils einmal mit POS drüber dauert 10-15ms.. Sonderlich langsam ist das nicht, oder?
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.

shmia 10. Feb 2010 16:56

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.

friedemann2009 10. Feb 2010 21:38

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

cookie22 10. Feb 2010 22:17

Re: TStringlist mit 60000 Einträgen zu langsam
 
FastMM gibs hier:http://sourceforge.net/projects/fastmm/

binde es einfach mal ins projekt ein und schau obs schneller geht.

und schau dir mal die faststrings.pas an: http://cc.embarcadero.com/Item/18628

himitsu 10. Feb 2010 22:19

Re: TStringlist mit 60000 Einträgen zu langsam
 
Delphi-Quellcode:
//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;
Versuch es erstmal hiermit.

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;

Hawkeye219 10. Feb 2010 23:06

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:
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;
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.

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

friedemann2009 11. Feb 2010 07:10

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:
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;
Ich versuche jetzt noch das mit FastMM.

Danke allen für die Tipps!
Grüße,
friedemann

himitsu 11. Feb 2010 07:20

Re: TStringlist mit 60000 Einträgen zu langsam
 
Zitat:

Zitat von Hawkeye219
aber ich vermute, dass die meiste Zeit nicht für das Lesen der Quelldaten, sondern für den Zusammenbau der Ergebnisdaten benötigt wird
...
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.

In neueren Delphiversionen hätte man auch einfach statt
Delphi-Quellcode:
ziel.Text := ziel.Text + ...;
einfach nur
Delphi-Quellcode:
ziel.Add(...);
machen können.

Beim Auslesen dann
Delphi-Quellcode:
ziel.LineBreak := ' ';
zielende := ziel.Text;
PS:
Delphi-Quellcode:
for x:=0 to ziel.Count-1 do
  zielende:= zielende + ' ' + ziel.Strings[x];
Diese hatte doch eh nichts gebracht.
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:
ziel.Add(...);

// und am Ende dann dein
for x:=0 to ziel.Count-1 do
  zielende:= zielende + ' ' + ziel.Strings[x];
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.

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.
Seite 2 von 5     12 34     Letzte »    

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