Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Geschwindigkeit einer Rekursion unter D2010 erhöhen (https://www.delphipraxis.net/148879-geschwindigkeit-einer-rekursion-unter-d2010-erhoehen.html)

kaju74 10. Mär 2010 08:37


Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Hallo.

Ich habe hier ein kleines Verständnisproblem, warum folgender Code extrem langsam wird, wenn es viele (XML) Unterelemente gibt:

Delphi-Quellcode:
function TXmlDocument.Content: string;

const
  CrLf = #13#10;
  Idnt = #32#32;

{ Expand }

  function Expand(const Str, Indent: string; const Elements: TXmlElements): string;
  var
    i: Integer;
    Content: string;
    SmartEnd: Boolean;
  begin
    Result := Str;
    for i := 0 to Elements.Count - 1 do
    begin
      Result := Result + Indent + '<' + Elements[i].Name;
      Content := Trim(Elements[i].Content);
      SmartEnd := not((Content <> '') or (Elements[i].Elements.Count > 0));
      if not(SmartEnd) then
        Result := Result + '>' + Content;
      if Elements[i].Elements.Count > 0 then
        Result := Expand(Result + CrLf, Indent + Idnt, Elements[i].Elements) + Indent;
      if not(SmartEnd) then
        Result := Result + Format('</%s>', [Elements[i].Name])
      else
        Result := Result + '/>';
      Result := Result + CrLf;
    end;
  end;

begin
  Result := '<?xml version="1.0" encoding="utf-8" ?>' + #13#10 + Expand('', '', Elements);
end;
Hierbei handelt es sich um eine kleine Routine, die rekursiv alle Xml-Elemente durchgeht und einen String
mit dem kompletten Inhalt zurückliefert. Bei einem Baum von 100 Knoten mit jeweils 50 Unterknoten braucht die
Routine auf meinem Rechner stolze 18 Sekunden.

Schritt1: String durch AnsiString ersetzen

Da ich derzeit sowie keine Unicode Xml-Dateien unterstütze und brauche, spare ich mir die ganzen Umwandlungen
und ersetze String durch AnsiString:

Delphi-Quellcode:
function TXmlDocument.Content: AnsiString;

const
  CrLf: AnsiString = #13#10;
  Idnt: AnsiString = #32#32;

{ Expand }

  function Expand(const Str, Indent: AnsiString; const Elements: TXmlElements): AnsiString;
  var
    i: Integer;
    Content: AnsiString;
    SmartEnd: Boolean;
  begin
    Result := Str;
    for i := 0 to Elements.Count - 1 do
    begin
      Result := Result + Indent + '<' + AnsiString(Elements[i].Name);
      Content := AnsiString(Trim(Elements[i].Content));
      SmartEnd := not((Content <> '') or (Elements[i].Elements.Count > 0));
      if not(SmartEnd) then
        Result := Result + '>' + Content;
      if Elements[i].Elements.Count > 0 then
        Result := Expand(Result + CrLf, Indent + Idnt, Elements[i].Elements) + Indent;
      if not(SmartEnd) then
        Result := Result + AnsiString(Format('</%s>', [Elements[i].Name]))
      else
        Result := Result + '/>';
      Result := Result + CrLf;
    end;
  end;

begin
  Result := '<?xml version="1.0" encoding="utf-8" ?>' + #13#10 + Expand('', '', Elements);
end;
Somit komme ich von ~18 Sekunden runter auf ~8 Sekunden. Nun habe ich aber folgendes Phänomen, das ich
nicht so ganz erklären kann:

Ersetze ich die beiden Konstanten am Anfang des Codes durch:

Delphi-Quellcode:
const
  CrLf: '';
  Idnt: '';
Werden aus den ~8 Sekunden schlanke 0,75 Sekunden (und aus den urspünglichen 18 Sekunden für Unicode etwa 4!).

WARUM???

Was kann ich tun, um trotzdem CarriageReturn/Linefeeds und Spaces in meiner Ausgabe zu erhalten, ohne das die
Geschwindigkeit wieder zusammenbricht?

Vielen Dank & Gruß,
kaju

jfheins 10. Mär 2010 08:41

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Das hat nichts mit der Rekursion an sich zu tun: Wiederholte String Concatenation (ist das richtig geschrieben?) ist generell nicht besonders schnell. Und wenn man dann noch eine Schleife drumherum baut, wirds auch nicht schneller ;)

Probier mal den StringBuilder aus ;)

himitsu 10. Mär 2010 09:37

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Oder eine "ordentliche" XML-Lib nutzen.

Zitat:

TXmlDocument.Content
Wer hat denn dieses überhaupt verbrockt?

PS: Diese Funktion ließe sich auch sehr leicht über eine StringList oder den genannten StringBuilder etwas optimieren.

kaju74 10. Mär 2010 10:13

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Hallo.

Die Lib ist schon älter...will die aber nicht unbedingt überall ersetzen müssen. Ich weiß, dass es wesentlich
bessere Libs wie OmniXML gibt, aber in einem großen, vorhandenen Projekt lässt sich das nicht mal eben schnell
ändern...das mit dem StringBuilder gucke ich mir mal etwas genauer an - Danke!

Lieben Gruß,
kaju

samso 10. Mär 2010 10:29

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Die eigentliche Frage
Zitat:

Was kann ich tun, um trotzdem CarriageReturn/Linefeeds und Spaces in meiner Ausgabe zu erhalten, ohne das die
Geschwindigkeit wieder zusammenbricht?
kann ich leider auch nicht beantworten. Ich finde diese Erscheinung auch erstmal erstaunlich. Ist die Compiler-Option "String-Formatprüfung" eingeschaltet? Wie ist es, wenn Du nur "Idnt: '';" setzt, aber CrLF=#13#10 lässt? Mir fällt eigentlich nur auf, dass der Compiler bei der Sequenz:
Delphi-Quellcode:
Result := Expand(Result + CrLf, Indent + Idnt, Elements[i].Elements) + Indent;
mit CrLf='' und Idnt='' keine temporären Strings für "Result + CrLf" und "Indent + Idnt" erstellen muss. Da auch Indent dann leer bleibt, reduziert sich der Aufruf dann in der Konsequenz auf
Delphi-Quellcode:
Result := Expand(Result, '', Elements[i].Elements));
Hier würde ich vielleicht ansetzten. Ich hoffe, ich habe keinen fürchterlichen Denkfehler gemacht, aber geht es auch so???
Delphi-Quellcode:
  procedure Expand(const Indent: string; const Elements: TXmlElements);
  var
    i: Integer;
    Content: string;
    SmartEnd: Boolean;
  begin
    for i := 0 to Elements.Count - 1 do
    begin
      Result := Result + Indent + '<' + Elements[i].Name;
      Content := Trim(Elements[i].Content);
      SmartEnd := not((Content <> '') or (Elements[i].Elements.Count > 0));
      if not(SmartEnd) then
        Result := Result + '>' + Content;
      if Elements[i].Elements.Count > 0 then
      begin
        Result := Result + CrLf;
        Expand(Indent + Idnt, Elements[i].Elements) + Indent;
      end;
      if not(SmartEnd) then
        Result := Result + '</' + Elements[i].Name + '>'
      else
        Result := Result + '/>';
      Result := Result + CrLf;
    end;
  end;
Falls ich mir das korrekt überlegt haben sollte, würde man ziemlich oft das Kopieren des Result-Strings vermeiden.

himitsu 10. Mär 2010 11:27

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
vesuch mal Dieses
Delphi-Quellcode:
function TXmlDocument.Content: string;
var
  SL: TStringList;

  procedure Expand(const Indent: string; Elements: TXmlElements);
  var
    i: Integer;
    S: String;
  begin
    for i := 0 to Elements.Count - 1 do
      if Elements[i].Elements.Count > 0 then begin
        SL.Add(Indent + '<' + Elements[i].Name + '>');
        Expand(Indent + ' ', Elements[i].Elements);
        SL.Add(Indent + '</' + Elements[i].Name + '>');
      end else begin
        S := Trim(Elements[i].Content);
        if S <> '' then
          SL.Add(Indent + '<' + Elements[i].Name + '>' + S + '</' + Elements[i].Name + '>')
        else
          SL.Add(Indent + '<' + Elements[i].Name + '/>');
      end
  end;

begin
  SL := TStringList.Create;
  try
    SL.Add('<?xml version="1.0" encoding="utf-8" ?>');
    Expand('', Elements);
    Result := SL.Text;
  finally
    SL.Free;
  end;
end;
Beim Original war nicht nur die große Laufzeit das Schlimme.
Schlecht war auch die Art der Parameter/Speicherverwaltung.
So wurde bei mehrfach verschalteten Nodes ein Vielfaches des Speichers belegt, welches auch noch mit zunehmender Anzahl der ChildNodes anwuchs, da der String über Str mehrfach verwaltet wurde. (welches samso auch schon gut beseitigt hat)

kaju74 10. Mär 2010 11:48

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Hallo.

Vielen lieben Dank - dieses Forum ist echt gold wert 8-))))) Werde das umgehend mal umsetzen.

Nochmals, vielen Dank.

Gruß,
- kaju

himitsu 10. Mär 2010 11:56

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Was mir gerade auffällt, alle diese Codes sollten ab D2009 Probleme in der Codierung bekommen, vorallem wenn du nicht aufpaßt.

String = Unicode
aber der XML-Header sagt es wäre UTF-8

OK, man könnte z.B. den UnicodeString jetzt einfach nach UTF-8 umkodieren und so speichern, aber dann muß der Text in Elements[i].Content auch als Unicode vorliegen und darf kein falsch codiertes UTF-8 enthalten.

kaju74 10. Mär 2010 12:10

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Nochmal Hallo.

Habe das mal eben eingebaut...schon krass...jetzt sind wir bei 0.015 Sekunden !!!! Was ein
Unterschied.

Nochmal Danke...

Wenn ich das richtig verstehe ist der große Unterschied, das bei der alten Lösung auch immer wieder
neuer Speicher allokiert werden musste während die Stringliste nur am Ende durchgeht und den
Resultatstring zusammenbaut.

Oje...machmal glaube ich, ich hab den Beruf verfehlt 8-)))

Lieben Gruß,
- kaju

kaju74 10. Mär 2010 13:03

Re: Geschwindigkeit einer Rekursion unter D2010 erhöhen
 
Nochnmal Hallo.

Um nun noch die fehlenden Attribute mit aufzunehmen, wäre das hier die schnellste Möglichkeit, oder macht man das dann wieder anders noch schneller?

Delphi-Quellcode:
function TNXXmlDocument.Content: string;
var
  ElementList: TStringList;
  AttributeList: TStringList;

{ ExpandElements }

  procedure ExpandElements(const Indent: string; Elements: TNXXmlElements);
  var
    Str: String;
    i, j: Integer;
  begin
    for i := 0 to Elements.Count - 1 do
    begin
      AttributeList.Clear;
      for j := 0 to Elements[i].Attributes.Count - 1 do
        AttributeList.Add(Elements[i].Attributes[j].Name + '="' + Elements[i].Attributes[j].Value + '"');
      if Elements[i].Elements.Count > 0 then
      begin
        ElementList.Add(Indent + '<' + Trim(Elements[i].Name + ' ' + AttributeList.DelimitedText) + '>');
        ExpandElements(Indent + ' ', Elements[i].Elements);
        ElementList.Add(Indent + '</' + Elements[i].Name + '>');
      end
      else
      begin
        Str := Trim(Elements[i].Content);
        if Str <> '' then
          ElementList.Add(Indent + '<' + Trim(Elements[i].Name + ' ' + AttributeList.Text) + '>' + Str + '</' + Elements[i].Name + '>')
        else
          ElementList.Add(Indent + '<' + Trim(Elements[i].Name + ' ' + AttributeList.Text) + '/>');
      end
    end;
  end;

begin
  ElementList := TStringList.Create;
  try
    ElementList.Add('<?xml version="1.0" encoding="utf-8" ?>');
    AttributeList := TStringList.Create;
    try
      AttributeList.QuoteChar := #0;
      AttributeList.Delimiter := ' ';
      ExpandElements('', Elements);
      Result := ElementList.Text;
    finally
      AttributeList.Free;
    end;
  finally
    ElementList.Free;
  end;
end;
Danke & Gruß,
- kaju


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:13 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz