Delphi-PRAXiS
Seite 1 von 2  1 2      

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)

friedemann2009 10. Feb 2010 12:36


TStringlist mit 60000 Einträgen zu langsam
 
Liebe Leute,

ich habe eine Liste in einer Stringlist mit teilweise über 60-100.000 Einträgen, die ich derzeit der Reihe nach abarbeiten muss. In jeder Zeile sind mit TAB getrennt 3 Wörter, von denen immer ein bestimmtes herausgezogen und in eine neue Stringlist übertragen wird.

Mein Problem: Wenn ich die Liste durchgehe, dauert es ewig: >5 Minuten für 30000 Einträge (Netbook mit 1,4 Ghz und 1GB RAM).

Hat jemand eine Idee, wie ich das (SEHR) viel schneller hinbekomme?

Danke für Eure Mühe und Hilfe, viele Grüße,
friedemann

PS: Delphi 5

Bernhard Geyer 10. Feb 2010 12:37

Re: TStringlist mit 60000 Einträgen zu langsam
 
Umstellen auf TListView mit virtuellen Modus
Umstellen auf VirtualStringTree mit virtuellen Modus
Umstellen auf TElTreeStringgrid (mit virtuellen Modus)

patti 10. Feb 2010 12:42

Re: TStringlist mit 60000 Einträgen zu langsam
 
Was du auch noch probieren könntest, wäre vor dem Abarbeiten bei beiden Stringlists ein

Delphi-Quellcode:
StringList.BeginUpdate;
aufzurufen. Nach dem Abarbeiten nicht das

Delphi-Quellcode:
StringList.EndUpdate;
vergessen! (Kann dir leider im Moment nicht sagen, ob Begin- bzw. EndUpdate einen Geschwindigkeitsvorteil für dich bringt).

mfg

himitsu 10. Feb 2010 12:44

Re: TStringlist mit 60000 Einträgen zu langsam
 
@Bernhard: StringList und nicht StringGrid (ich geh mal davon auß, daß er sich da nicht verschrieben hat)

Wie verarbeitesten du denn diese Liste
und wie groß ist diese (der Text in Byte)?

100.000 Einträge ist jetzt nicht sehr viel.

Erstmal könntest du den langsamen Speichermanager von Delphi austauschen > Bei Google suchenFastMM
(in neueren Delphis wurde er schon ausgetauscht)

Und jetzt kann man erstmal nur noch deine Verarbeitung versuchen zu optimieren, aber diese kenne wir noch nicht.


PS: BeginUpdate bringt bei einer reinen/alleinstehenden StringListe nicht viel, da dieses OnChange-Ereignisse und ähnliches, wie GUI-Aktualisierungen unterbindet, welche es bei einer enzelnen TStringList nicht unbedingt gibt.

Mithrandir 10. Feb 2010 12:47

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

Zitat von himitsu
Und jetzt kann man erstmal nur noch deine Verarbeitung versuchen zu optimieren, aber diese kenne wir noch nicht.

Eben. Ich denke, hier liegt der Flaschenhals. Vielleicht zeigts du mal etwas Code. ;)

mschaefer 10. Feb 2010 12:48

Re: TStringlist mit 60000 Einträgen zu langsam
 
Moin, moin,

Zu Listen gibt es in der DP einen interesanten Thread: Vergleich von Suchverfahren in Listen

Grüße // Martin

hoika 10. Feb 2010 12:49

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo,

ich würde mal MemCheck (oder FastMM) nehmen,
dann die Liste füllen und das Programm ohne List.Free beenden.
Dann bekommst du genau raus, wie viel Speicher die Liste braucht.

Oder für grobere Schätzung den Task-Manager nehmen.


Die 1 GB RAM könnten schon das Problem sein,
wenn Delphi auch noch offen ist.
Zumal du ja einen Teil der Strings in eine 2. Liste packst,
die dann auch noch Speicher frißt (ähem isst ;) ).



Heiko

Tyrael Y. 10. Feb 2010 13:24

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

Zitat von Daniel G
Zitat:

Zitat von himitsu
Und jetzt kann man erstmal nur noch deine Verarbeitung versuchen zu optimieren, aber diese kenne wir noch nicht.

Eben. Ich denke, hier liegt der Flaschenhals. Vielleicht zeigts du mal etwas Code. ;)

Genau dies wird die Stelle sein, die Zeit kostet, da Stringoperationen nicht gerade sehr schnell sind.

Wenn ich in blaue rate, würde ich sagen, daß das Benutzen der StringListe selbt auch schon ein Fehler ist.
Du sagst in jedem Listeintrag steht ein String, der mit TAB getrennt drei Wörter enthält und du nur
diesen String auseinandernimmst, um an deinen gewünschten String zu kommen.

Du solltest eher eine Datenklasse mit drei Feldern benutzen und diese dann in TObjectList schreiben.
Der Vorteil der Datenklasse ist, daß du nicht mühsam etwas aueinander nimmst, sondern direkt den gewünschten Wert hast.

Beispiel:
Delphi-Quellcode:

type
  TMeineDaten = class
   public
     property Bezeichner: String;
     property Strasse: String;
     property Ort: String;
  end;

  ....

  ....
var
  myList: TObjectList;
...
  neueDaten := TMeineDaten.Create();
  neueDaten.Bezeichner := "Stefan";
...
  myList.add(neueDaten);
...
...
  for i := 0 to myList.Count-1 do
  begin
    datenAusListe := TMeinDaten(myList[i]);
    Edit1.Text := datenAusListe.Bezeichner;
    ....
  end;
...
Alles nur ein Beispiel. Ich denke das wird dein Problem sein.

Bernhard Geyer 10. Feb 2010 14:09

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

Zitat von himitsu
@Bernhard: StringList und nicht StringGrid (ich geh mal davon auß, daß er sich da nicht verschrieben hat)

Geh zur Wand und ... :wall:


Je nach Aktionen die du auf der Stringlist (Suchen, IndexOf, ...) machst wäre eine Hash-Table oder B-Tree günstiger.

exilant 10. Feb 2010 14:11

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

Zitat von friedemann2009
Liebe Leute,

Mein Problem: Wenn ich die Liste durchgehe, dauert es ewig: >5 Minuten für 30000 Einträge (Netbook mit 1,4 Ghz und 1GB RAM).

Hat jemand eine Idee, wie ich das (SEHR) viel schneller hinbekomme?

Danke für Eure Mühe und Hilfe, viele Grüße,
friedemann

PS: Delphi 5

Also: Ich erzeuge hier eine Liste mit 500.000 Einträgen (zufällig erzeugte Strings). Das Füllen dauert auf einem
Acer nettop mit 1,6 Ghz und 1GB Ram gute 3 Sekunden, die Liste enthält danach ca. 65MB Daten.

Das durchsuchen der Stringliste nach einem ebenfalls zufällig generierten Teilstring mit anschliessendem umkopieren eines viertels der Liste in eine neue Liste in zufälligen Abständen dauert weniger als 1 Sekunde.
Am Rechner und an TStringlist kann es nicht liegen...

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;

hoika 11. Feb 2010 07:32

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo,

Zitat:

also ein bisschen hat es was gebracht, aber nicht viel..
Du stocherst also etwas rum und es bringt nicht viel.

Warum findest du nicht erst mal raus, was so bremst ?

Entweder durch einen Profiler oder "auskommentieren".

Ich würde z.B. erst mal das ganze "Wort suchen" durch
"kompletten String nehmen" ersetzen und testen.


#Update#
Delphi-Quellcode:
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;
Wieso muss hier eine StringList erzeugt werden ?
OK, das ominöse ;) extractstrings will das haben.
Wie sieht denn der Code extractstrings aus ?

[char(sep)] ein offenes Array mit nur einem Zeichen, wozu dann ein array ?

Wie es aussiehtst, missbrauchst du die StringList hier,
du gibst sie ja auch wieder frei.


Heiko

Uwe Raabe 11. Feb 2010 08:36

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

Zitat von hoika
[char(sep)] ein offenes Array mit nur einem Zeichen, wozu dann ein array ?

...oder ein set of Char?

hoika 11. Feb 2010 08:40

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo,

ja oder so.
Ist hier aber überhaupt nicht notwendig, nur langsamer.


Heiko

Reinhard Kern 11. Feb 2010 09:05

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo,

ein Text mit TAB-separierten Daten ist doch ein klassischer Fall für einfach von Anfang bis Ende durchparsen - von der Logik her ist es völlig unnötig mehr als eine aktuelle Zeile überhaupt im Speicher zu haben. Wieso muss man hier in der DP immer für einfachste Aufgaben möglichst komplizierte VCL-Elemente benutzen? Auf Ruhm und Ehre unter fanatischen Code-Ideologen kann ich gern verzichten, dafür habe ich dann einfache und schnelle Programme.

Einen durchgehenden Text seriell zu verarbeiten ist keineswegs steinzeitlich und hinterwäldlerisch, das machen auch professionelle gekaufte Programme so.

Meinetwegen kann jetzt die Prügelei losgehen, ich werde trotzdem so weiter programmieren, ich muss ja hier nichts davon veröffentlichen.

Gruss Reinhard

friedemann2009 11. Feb 2010 10:20

Re: TStringlist mit 60000 Einträgen zu langsam
 
@Reinhart, was meinst du damit?

Gruß, friedemann

Jaynder 11. Feb 2010 10:23

Re: TStringlist mit 60000 Einträgen zu langsam
 
Also auf die schnelle ist mir aufgefallen, das friedemann

1. lokal ständig neue Objekte anlegt und wieder freigibt, was bei diesen Größenordungen nicht zu vernachlässigen ist
2. die string-Parameter als "call by value" übergibt, was bedeuetet, das jedesmal eine Kopie angelegt und wieder freigegeben wird; "const" würde da sehr helfen

Ansonsten stimme ich Reinhard zu, ein ordentlicher Parser ist mit Sicherheit die beste Lösung.

hoika 11. Feb 2010 10:28

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo,

Zitat:

von der Logik her ist es völlig unnötig mehr als eine aktuelle Zeile überhaupt im Speicher zu haben.
Einspruch Euer Ehren ;)

Das Zusammenbauen der Ziel-"Datei" im Speicher
würde ich viell. auch so machen.
Dann geht das mit dem "Ganz oder gar nicht Speichern" einfacher
(OK, man könnte die "halbe" Datei bei einem Fehler wieder löschen)

Das "Ganz oder gar nicht Speichern" war ja jetzt keine Anforderung,
könnte aber sein ... ;)

Ich denke, das Grundproblem sind die "Hole Wort X" Aufrufe.
Ob ich jetzt die Ausgangsdatei zeilenweise bearbeite
oder alles in eine StringListe packe, ändert daran nichts (vom Speucher abgesehen).

Ich würde deshalb ganz gern mal den ExtractStrings-Code sehen.
Gab es hier nicht mal eine Diskussion über Explode ?


Bei schlechter Laufzeit sollte eh ein Profiler genommen werden,
sonst wird an der falschen Stelle optimiert.


Zu const
Ist mir noch gar nicht aufgefallen ;)

An den TE
also aus
Delphi-Quellcode:
function gibmirLemma(s:string; sep:char) :string;
wird

Delphi-Quellcode:
function gibmirLemma(const s:string; sep:char) :string;

Heiko

Jaynder 11. Feb 2010 11:18

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

Zitat von hoika
Hallo,
Wird nicht seit D2 eh nur eine Zeiger übergeben ?
Heiko

Stimmt schon (weiß zwar nicht mehr seit wann), aber trotzdem passiert Speicherverwaltungstechnisch eine ganze Menge mehr, wenn der Parameter lokal verändert werden darf.

Aus
Delphi-Quellcode:
function test1(const s:string; sep:char) :string;
begin
  Result := S;
end;
wird kurz und knapp
Code:
Main.pas.106: begin
push ebx
push esi
mov esi,ecx
mov ebx,eax

Main.pas.107: Result := S;
mov eax,esi
mov edx,ebx
call @UStrAsg

Main.pas.108: end;
pop esi
pop ebx
ret
dagegen bläht sich
Delphi-Quellcode:
function test2(s:string; sep:char) :string;
begin
  Result := S;
end;
ganz fürchterlich zu
Code:
Main.pas.101: begin
push ebp
mov ebp,esp
push ecx
push ebx
mov ebx,ecx
mov [ebp-$04],eax
mov eax,[ebp-$04]
call @UStrAddRef
xor eax,eax
push ebp
push $004a3bcc
push dword ptr fs:[eax]
mov fs:[eax],esp

Main.pas.102: Result := S;
mov eax,ebx
mov edx,[ebp-$04]
call @UStrAsg

Main.pas.103: end;
xor eax,eax
pop edx
pop ecx
pop ecx
mov fs:[eax],edx
push $004a3bd3
lea eax,[ebp-$04]
call @UStrClr
ret
jmp @HandleFinally
jmp $004a3bc3
pop ebx
pop ecx
pop ebp
ret
aus. Man muss das ja nicht alles verstehen, aber bei zeitkritischen Sachen summiert sich das ganz erheblich und man sollte es im Hinterkopf behalten. Ich schreibe inszwischen bei Strings ganz automatische immer const davor.

himitsu 11. Feb 2010 11:50

Re: TStringlist mit 60000 Einträgen zu langsam
 
Delphi-Quellcode:
Ich schreibe inszwischen bei Strings ganz automatische immer const davor.
Jupp, am Besten man gewöhnt sich sowas allgemein an, so ist man vor mehrere unangenehmen überraschungen besser geschützt.
(also einfach bei allem, welches nicht direkt mit Integern, Chars, Booleans und Objekten zu tuen hat)

hoika 11. Feb 2010 12:04

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo,

wie ihr seht, habe ich die entsprechende Passage schon gelöscht ;)


Heiko

Reinhard Kern 11. Feb 2010 15:25

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

Zitat von Hawkeye219
Warum nimmst du eigentlich den Umweg über eine Stringliste? Am Ende fügst du ja doch wieder alles zu einem einzelnen String zusammen.

Hallo Hawkeye, das gleiche frage ich ja bezüglich der Eingangsdaten, aber das wird entweder niedergemacht oder ignoriert - statt einfach weitere Zeilen an den Ausgangsdatei anzuhängen (simples Schreiben in eine Datei), muss mit hundertausenden von stringreplace-Aufrufen dafür gesorgt werden, das das Ergebnis am Ende so aussieht, wie es ohne jeden Aufwand von vornherein hätte aussehen können.

Eine einfache readln - verarbeiten - writeln Sequenz ist zwar viel effektiver, aber verpönt.

Zitat:

Zitat von friedemann2009
@Reinhart, was meinst du damit?

Gruß, friedemann

Hallo Friedemann, wie du siehst, kann man das hier im Forum nicht besprechen, einfache und effektive Programme sind bei der überwiegenden Mehrheit absolut unerwünscht. Also vergiss es einfach.

Gruss Reinhard

himitsu 11. Feb 2010 15:54

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

Zitat von Reinhard Kern
Hallo Friedemann, wie du siehst, kann man das hier im Forum nicht besprechen, einfache und effektive Programme sind bei der überwiegenden Mehrheit absolut unerwünscht. Also vergiss es einfach.

Ideal wäre es mal, OOP-mäßig eine neue Klasse zu erstellen, welche die Vorteile der "alten" Pascalfunktionen mit einigen Verbesserungen/Optimierungen vereint.
Also eine Art TStringStream, welchen man an alle möglichen Streams ranhängen kann.
Vorallem an TFileStream und TMemoryStream.

Ja, es gibt einen TStringStream, aber der ist ja wohl ein Witz.

Ideen dafür existieren schon länger, aber ich hatte nie einen Grund, dieses mal zu machen. :oops:


Ein großes Problem exisitert nämlich, denn mit den alten Pascal-Routinen kann man selbst ab D2009 nur ANSI-Dateien erstellen und auslesen.

Hawkeye219 11. Feb 2010 16:31

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

Zitat von himitsu
Ideal wäre es mal, OOP-mäßig eine neue Klasse zu erstellen, welche die Vorteile der "alten" Pascalfunktionen mit einigen Verbesserungen/Optimierungen vereint.
Also eine Art TStringStream, welchen man an alle möglichen Streams ranhängen kann.
Vorallem an TFileStream und TMemoryStream.

auch wenn es dem TE aufgrund seiner eingesetzten Delphi-Version nicht hilft, passt es thematisch noch in diesen Thread:

TStreamReader/TStreamWriter
TStringReader/TStringWriter

Gruß Hawkeye

Reinhard Kern 11. Feb 2010 16:32

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

Zitat von himitsu
Ein großes Problem exisitert nämlich, denn mit den alten Pascal-Routinen kann man selbst ab D2009 nur ANSI-Dateien erstellen und auslesen.

Falls das ein Problem ist oder wird, es soll auch unter Windows API Möglichkeiten geben, aus einer Datei zu lesen. Klassen für Textdateien bzw. eine Hierarchie davon habe ich mir auch schon erstellt, dabei habe ich teilweise auch eigene readln und writeln Prozeduren verwendet; das ist ja schliesslich kein Hexenwerk, das nur Borlandprogrammierer hinkriegen.

Gruss Reinhard

p80286 11. Feb 2010 16:39

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo zusammen,
ist ja richtig was los hier.

wie ja schon mehrmals angemerkt wurde ist
Delphi-Quellcode:
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;
nicht so optimal.

wie wäre denn z.B. so etwas:
Delphi-Quellcode:
function gibmirLemma(s:string; sep:char) :string;
var
  pp : integer;  
begin
  result:='';
  //hier muss jetzt das zweite Wort rausgefiltert werden
  pp:=pos(sep,s);
  if pp>0 then begin
    pp:=posex(sep,s,pp+1);
    if pp>0 then
      result:= copy(s,pp+1,255);
  end;
end;
@reinhard
Auch wenn ich Dir im Prinzip zustimme, TStringlist ist einfach genial, besonders wenn die Verarbeitung ein satz vor zwei Satz zurück, einen löschen läuft.

Wenn es wirklich nur darum geht (und das hab ich noch nicht erkannt) einen Satz nach dem anderen zu lesen, zu filetieren und dann wegzuschreiben/an einen String anzuhängen würde ich Dich inhaltlich voll unterstützen.

und verlier bitte den Unterschied nicht aus den Augen:

Delphi-Quellcode:
var
  sl=tstringlist;


sl:=tstringlist.create;
...
sl.loadfromfile('MeineDaten');
for i:=0 to sl.count-1 do
  irgendwas
sl.free;
Delphi-Quellcode:
var
  f     : textfile;
  buffer : array [0..8191] of byte;
  satz  : string


assign(f,'MeineDaten');
settextbuf(f,buffer,sizeof(buffer));
reset(f);
repeat
  readln(f,satz);
  machwwasmitsatz
unil eof(f);
closefile(f);
Die zweite Möglichkeit ist doch wesentlich aufwendiger.

Gruß
K-H

Chemiker 11. Feb 2010 19:11

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo friedemann2009,

wie viel Speicher verbrauchst Du für die 100.000 Einträge?
Welches Windows ist auf dem Rechner installiert?

Die geringe Geschwindigkeit könnte auch von Windows verursacht werden, wenn zu viel Speicher belegt wird. Wenn der Speicher voll ist, fängt Windows an Teile davon auf die Festplatte auszulagern.

Bis bald Chemiker

David Martens 11. Feb 2010 23:11

Re: TStringlist mit 60000 Einträgen zu langsam
 
Hallo,
jetzt geb ich auch mal meinen Senf dazu.
Das was hier die meiste Zeit braucht sind doch die vielen gibmir... Aufrufe.

Hier wie ich es machen würde:
Delphi-Quellcode:
    begin
      quelle := tstringlist.create;
      ziel := tstringlist.create;
      eineZeile := tstringlist.create;
      try
        //Previewdatei laden
        quelle.LoadFromFile(extractfilepath(application.exename) + 'preview2.dat');

        eineZeile.Delimiter := #9;
        eineZeile.QuoteChar := '"'; // hier bin ich mir nicht sicher ob das reicht um die stringreplace überflüssig zu machen

        for i := 0 to quelle.Count - 1 do
        begin
          // vorsichtshalber säubern, muß aber nicht sein
          eineZeile.Clear;
          // nur 1 mal parsen (und das von Delphi selbst) statt x mal immerwieder
          // hier kann wahlweise auch mit readln gearbeitet werden wenn man will
          eineZeile.DelimitedText := quelle.Strings[i];

          //Token zusammennehmen
          if pos('#' + eineZeile[1] + '#', tok) <> 0 then
            ziel.text:= ziel.text + eineZeile[0];

          //Lemma zusammennehmen
          if pos('#' + eineZeile[1] + '#', lem) <> 0 then
            begin
              schon := 0;

              if eineZeile[2] in ['<UNKNOWN>', '@card@', 'CARD', '@ord@'] then
              begin
                if (eineZeile[2] = '<UNKNOWN>') then
                begin
                  if checkbox2.checked then
                  begin
                    ziel.text := ziel.text + eineZeile[0];
                    schon := 1;
                  end;
                end
                else // alle anderen
                begin
                  if checkbox4.checked then
                  begin
                    ziel.text := ziel.text + eineZeile[0];
                    schon := 1;
                  end;
                end;
              end;

              if schon = 0 then
                ziel.text := ziel.text + eineZeile[2];
            end;

          if pos('#' + eineZeile[1] + '#', poss) <> 0 then
            ziel.Text := ziel.text + eineZeile[1];
         end;

        for i := 0 to ziel.Count - 1 do
          zielende := zielende + ' ' + ziel.Strings[i];

        memo2.text := zielende;

      finally
        quelle.Free;
        ziel.Free;
        eineZeile.Free;
      end;
Damit wird nur 1 mal geparst und dann auf die Teile direkt zugegriffen.
Ob das jetzt 100 pro funktioniert kann ich aber nicht so aus dem Hut sagen.

Um zur Diskusion beizutragen: soweit ich mich erinnern kann nutzt LoadFromFile u.Ä. die Windows API für das eigentliche Ansprechen der Datei, während readln eine Delphi (besser Pascal) eigene Umsetzung der gleichen Funktion ist. An dieser Stelle lässt sich darüber streiten, ob die Windows eigene oder die Borland/CodeGear/Embarcadero umsetzung der selben schneller ist. Ich bin ja (auch wenn ich das sonst nicht so sehe) eher für MS, alleine durch die Tatsache das MS die Funktionen auf die jeweilige Windows Version anpassen kann und Delphi von der Plattform wo das Programm dann läuft zur Compiletime keine Ahnung haben kann.

Bis Dann, und hoffentlich konnte ich helfen,
David

Nachtrag: Ich hab nochmal zusammegerechnet, du zerpflückst jede Zeile 5 mal, da hast du dein Performanceproblem. Wenn es so wie in meinem Code nicht geht kannst du auch eine Funktion schreiben die alle 3 Teile auf einmal zurückgibt und dann im "Hauptprogramm" in 3 einzelne Variablen speichert. So in etwa:
Delphi-Quellcode:
procedure aufteilen(const s : string; const sep : char; var Token, wortart, Lemma : string);
var
  t: Tstringlist;
begin
  //hier muss jetzt das zweite Wort rausgefiltert werden
  t:= tstringlist.create;
  try
    extractstrings([char(sep)], [' '], pchar(s), t);
    Token  := t.Strings[0];
    wortart := t.Strings[1];
    Lemma  := t.Strings[2];
  finally
    t.free;
  end;
end;

// so wirds dann benutzt, einmal am Anfang
aufteilen(quelle.strings[ii], #9, token_Variable, wortarttemp, lemmareal);

jensw_2000 11. Feb 2010 23:35

Re: TStringlist mit 60000 Einträgen zu langsam
 
Eventuell tut es in diesem speziellen Fall auch schon
Delphi-Quellcode:
meineStringliste.sorted := true;
?

David Martens 11. Feb 2010 23:38

Re: TStringlist mit 60000 Einträgen zu langsam
 
Die Sortierung ist, glaube ich, schon wichtig. Mit .sorted := true; geht die verloren.

friedemann2009 12. Feb 2010 13:31

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

Zitat von Chemiker
Hallo friedemann2009,

wie viel Speicher verbrauchst Du für die 100.000 Einträge?
Welches Windows ist auf dem Rechner installiert?

Die geringe Geschwindigkeit könnte auch von Windows verursacht werden, wenn zu viel Speicher belegt wird. Wenn der Speicher voll ist, fängt Windows an Teile davon auf die Festplatte auszulagern.

Bis bald Chemiker

Also wenn ich es richtig verfolge, wird nichts ausgelagert. Windoof XP prof. Ich fürchte auch, dass es an den "gibmir"-Funktionen und dem Replace liegt. Ich bin am Nachprobieren, was hier so bisher geschrieben wurde.

Danke und Gruß,
friedemann


NACHTRAG:

- Falls ich es noch nicht schrieb: Thanks a lot für die vielen Hinweise!


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:00 Uhr.
Seite 1 von 2  1 2      

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