Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi #0 perfomanceschonend aus String entfernen (https://www.delphipraxis.net/147797-0-perfomanceschonend-aus-string-entfernen.html)

Helmi 16. Feb 2010 15:52


#0 perfomanceschonend aus String entfernen
 
Hallo,

ich lade mittels folgenden Code eine Text-Datei in mein Programm, dass #0 enthält:
Delphi-Quellcode:
  //FileStream und StringStream erzeugen
  FileStream   := TFileStream.Create(Datei, fmOpenRead);
  StringStream := TStringStream.Create(S);

  try
    //FileStream in StringStream kopieren
    FileStream.Position := 0;
    StringStream.CopyFrom(FileStream, FileStream.Size);

    //String aus StringStream auslesen
    StringStream.Position := 0;
    S := StringStream.ReadString(StringStream.Size);
In "S" hab ich dann den String der Datei aber noch mit #0.
Um dieses "Zeichen" jetzt zu entfernen, nutz ich folgenden Code:
Delphi-Quellcode:
    //#0 durch #32 ersetzen
    for i := 1 to length(S) do
      begin
        If S[i] = #0 then
          S[i] := #32;
      end;
Nur ist das jetzt nicht sehr perfomance-schonend.
Vor allem, weil die einzulesende Datei gute 4 MB hat und dementsprechend Zeichen.

Gibt es dafür eine bessere Lösung?

Noch kurz als Info: Momentan arbeite ich dann mit "S", also dem String, weiter.
Ich möchte aber nun das Ganze in eine StringList laden.

Tyrael Y. 16. Feb 2010 15:57

Re: #0 perfomanceschonend aus String entfernen
 
Delphi-Quellcode:
meinString := Trim(meinString);
?

mkinzler 16. Feb 2010 15:58

Re: #0 perfomanceschonend aus String entfernen
 
Man könnte auch mit StringReplace() arbeiten. Das wird aber im Endeffekt auf das selbe herauskommen

shmia 16. Feb 2010 16:05

Re: #0 perfomanceschonend aus String entfernen
 
Wenn man mit Zeigern arbeitet, dann sollte das Ratz-Fatz gehen (nur Assembler wäre schneller):
Delphi-Quellcode:
var
  p : PChar;
  i : integer;
begin
  UniqueString(S);
  p := PChar(S);
  for i := 1 to length(S) do
  begin
    If p^ = #0 then
      p^ := #32;
    Inc(p);
  end;
Wenn man "an der RTL vorbei" Strings über Zeiger verändert muss man vorher UniqueString() aufrufen.

p80286 16. Feb 2010 16:06

Re: #0 perfomanceschonend aus String entfernen
 
Warum die ganze streamerei?
Delphi-Quellcode:
var
  sll : tstringlist;

sll.loadfromfile('MeineDatei');
sll.Text:=SringReplace(sll.Text,#0,#32); { oder eine andere Ersetzungsroutine }
...
Wegen 4MB macht man sich doch nicht mehr ins Hemd *G*

Gruß
K-H

himitsu 16. Feb 2010 16:26

Re: #0 perfomanceschonend aus String entfernen
 
TStringList hört bei #0 auf mit einlesen, also fehlt dann nach dem Einlesen so Einiges,
Aber den TStringStream hätte man sich wohl Sparen können.

Und wie schon gesagt, 4 MB ist für diese FOR-Schleife doch garnichts. :gruebel:

Aber wenn es unbedingt sein muß
Delphi-Quellcode:
var MS: TMemoryStream;
  S: AnsiString;
  i: Integer;
begin
  MS.LoadFromFile(Datei);
  For i := MS.Size - 1 downto 0 do
    If PAnsiChar(MS.Memory)[i] = #0 Then PAnsiChar(MS.Memory)[i] := ' ';
  SetLength(S, MS.Size);
  MoveMemory(@S[1], MS.Memory, MS.Size);
  StringList.Text := S;
oder gleich
Delphi-Quellcode:
var MS: TMemoryStream;
  i: Integer;
begin
  MS.LoadFromFile(Datei);
  For i := MS.Size - 1 downto 0 do
    If PAnsiChar(MS.Memory)[i] = #0 Then PAnsiChar(MS.Memory)[i] := ' ';
  StringList.LoadFormStream(MS);

Helmi 16. Feb 2010 16:42

Re: #0 perfomanceschonend aus String entfernen
 
Hallo,

danke an alle für eure Antworten!

Mir kam das Char-weise durchsteppen durch den String etwas unprofessional (billig) vor.

Aber Himitsu´s MemoryStream-Lösung gefällt mir sehr gut.

p80286 16. Feb 2010 16:51

Re: #0 perfomanceschonend aus String entfernen
 
Zitat:

Zitat von himitsu
TStringList hört bei #0 auf mit einlesen, also fehlt dann nach dem Einlesen so Einiges,

ist ja gut, nicht schlagen,ich kann mich an diese #0 einfach nicht gewöhnen.
Gruß
K-h

shmia 16. Feb 2010 17:14

Re: #0 perfomanceschonend aus String entfernen
 
Zitat:

Zitat von Helmi
Mir kam das Char-weise durchsteppen durch den String etwas unprofessional (billig) vor.

Das scheint nur so.
"Professionel" ist wenn der Sourcecode einfach strukturiert und und quasi selbsterklärend ist:
Delphi-Quellcode:
function StrReplaceChar(const S: Ansistring; const Source, Replace: AnsiChar): string;
var
  I: Integer;
begin
  Result := S;
  // hier besteht noch Optimierungsmöglichkeit durch Verwendung eines Zeigers
  for I := 1 to Length(S) do
    if Result[I] = Source then
      Result[I] := Replace;
end;
und dann später:
Delphi-Quellcode:
Memo1.Lines.Text := StrReplaceChar(StringStream.DataString, #0, ' ');
Nix gegen Himitsu, aber die Lösung aus Betrag #6 mit dem Memorystream ist deutlich schwerer zu lesen.
Der StringStream ist dem Memorystream vorzuziehen, da es viel sicherer und einfacher ist mit Strings zu arbeiten.
(du hast nur noch nicht mitgekriegt, dass man über das Property DataString direkt auf den Inhalt zugreifen kann)
Die potentiell "lebensgefährliche" Funktion MoveMemory() wird vermieden und das ist sehr wichtig für die Softwarequalität.
MoveMemory() ist wie eine geladene Schrotflinte unterm Bett - sie kann dir jederzeit den ganzen Fuss wegschiesen!

p80286 16. Feb 2010 17:35

Re: #0 perfomanceschonend aus String entfernen
 
@shmia
Zitat:

[/delphi]
// hier besteht noch Optimierungsmöglichkeit durch Verwendung eines Zeigers
for I := 1 to Length(S) do
if Result[I] = Source then
Result[I] := Replace;
[delphi]
warum?
Du hast das #4 ja schon angedeutet, aber das
[pre]
For i:=1 to length(s)
...
inc(p)
[/pre]
scheint mir "doppeltgemoppelt" und daher eigentlich ein Performance Killer.

Wenn' unbedingt ein Pointer sein muß hätte ich auf @s[i] getippt;

Gruß
K-H

shmia 16. Feb 2010 17:52

Re: #0 perfomanceschonend aus String entfernen
 
Zitat:

Zitat von p80286
scheint mir "doppeltgemoppelt" und daher eigentlich ein Performance Killer.

Der Performance Killer ist die Indizierung durch den Array-Index.
Wenn da z.B. steht: s[i] , dann wird intern der Zeiger s genommen, dann wird i dazuaddiert, dies ergibt dann den Zeiger auf das zu lesende oder schreibende Zeichen.
Bei einem 4MB-String wären das 8 Millionen unnötige Additionen.

Hier die komplett getunte Funktion:
Delphi-Quellcode:
function StrReplaceChar(const S: Ansistring; const Source, Replace: AnsiChar): AnsiString;
var
  I: Integer;
  p : PAnsiChar;
begin
  Result := S;
  UniqueString(Result);
  p := PAnsiChar(Result);
  for I := Length(S)-1 downto 0 do
  begin
    if p^ = Source then
      p^ := Replace;
    Inc(p);
  end;
end;
Hier wird der Zeiger p einmal gesetzt und dann immer nur mit Inc() weiterbewegt.
Inc(p) wird direkt in einen einzigen (schnellen) X86-Befehl übersetzt.
Da die Schleifenvariable nicht mehr benützt wird, kann man die Schleife auch rückwärts laufen lassen.
Schleifen, die rückwärts runter auf 0 zählen sind besonders gut in Maschinencode zu übersetzen und daher sehr schnell.

alzaimar 16. Feb 2010 18:39

Re: #0 perfomanceschonend aus String entfernen
 
[quote="shmia"]
Zitat:

Zitat von p80286
Bei einem 4MB-String wären das 8 Millionen unnötige Additionen.

Ist das nicht eher indirekte Adressierung? Und was ist eigentlich das 'Inc(P)'? Ist das nicht auch eine Addition?

Meiner Erfahrung nach ist der Unterschied in diesem Fall marginal. Und wenn ich es ausprobiere, sehe ich auch keinen Unterschied. Ich würde dann die naive For-Schleife und einen stinknormalen String mit Index verwenden:
Delphi-Quellcode:
For i:=1 to Length(s) do
  if s[i]=ZuErsetzendesZeichen then
    s[i] := NeuesZeichen;
Zitat:

Zitat von shmia
Hier die komplett getunte Funktion:...Schleifen, die rückwärts runter auf 0 zählen sind besonders gut in Maschinencode zu übersetzen und daher sehr schnell.

Hmm.. Zählt Dephi nicht automatisch rückwärts, wenn es keine Auswirkungen hat?
Hast Du wirklich verifiziert, das deine getunte Funktion schneller ist? Bei mir ist sie das nämlich nicht.

Ich stolpere immer wieder über diesen Vergleich zwischen Pointer und Indizierung. Bei solch naiven Schleifen sind beide gleich schnell, aber sobald man etwas mehr in der Schleife macht, ist die Pointer-Variante immer etwas schneller (20-30%, na ja, 'etwas'...)

Um es schneller zu bekommen, würde ich mir mal FastCode reinziehen, da gibt es eine 'CharPos' Funktion, die mit Sicherheit sehr viel schneller ist als die einfache Schleife. Sie verwendet Fluxkompensatoren, vierdimensionale Zeitfalten und *tunnelt* den Code, sodaß das das Programm fertig ist, bevor man überhaupt auf start gedrückt hat. Glaub ich jedenfalls.

himitsu 16. Feb 2010 19:34

Re: #0 perfomanceschonend aus String entfernen
 
Blos noch etwas Zusätzliches:

PChar(S) gibt nur einen Zeiger auf das erste String-Zeichen oder bei einem Leerstring auf einen "anderen" Bereich, welcher auf 0000 steht.

@S[1] ruft UniqueString auf und gibt einen Zeiger auf das 1. Zeichen oder nil zurück.

StringReplace und #0 im zu Suchstring, sowie im zu durchsuchendem String geht nicht, da CodEmba blöder Weise intern eine auf PChar-basierende Pos-Funktion nutzen, welche ja bekanntlich bei #0 stoppen. :wall:

PS: diese Move-Funktionen sind insoweit nicht gefährlich, wie man auf die Datengößen achtet und weiß was man tut.
Bei dem StringStream muß man in diesem Fall ab D2009 auch aufpassen, daß man nicht durch ein falsches/unpassendes Encoding die Daten zerschrottet.
PSS: Aus soeinem Grund hatte ich auch extra den AnsiString, statt String verwendet.

p80286 17. Feb 2010 10:03

Re: #0 perfomanceschonend aus String entfernen
 
Hallo zusammen

ich hab mich dann mal aufgemacht und quickndirty versucht heraus zu bekommen was es in der Praxis bring:
Zitat:

---------------------
2063 1.lauf
---------------------
2000 2.lauf
Delphi-Quellcode:
function StrReplaceChar(const S: Ansistring; const Source, Replace: AnsiChar): AnsiString;
var
  I: Integer;
  p : PAnsiChar;
begin
  Result := S;
  UniqueString(Result);
  p := PAnsiChar(Result);
  for I := Length(S)-1 downto 0 do
  begin
    if p^ = Source then
      p^ := Replace;
    Inc(p);
  end;
end;

function StrReplaceChar_(const S: Ansistring; const Source, Replace: AnsiChar): AnsiString;
var
  I: Integer;
begin
  Result := S;
  UniqueString(Result);
  for I := Length(S)-1 downto 0 do
    if result[i] = Source then
      result[i] := Replace;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i,j : integer;
  start1,
  start2,
  end1,
  end2 : integer;
  ts  : string;
begin
  setlength(ts,4096*1024);

  fillchar(ts[1],length(ts),#32);
  for i:=1 to 4096 do
    if i mod 13 =0 then ts[i]:=#0;
  start1:=gettickcount;
  for j:=0 to 255 do
    StrReplaceChar(ts,#0,'A');
  end1:=gettickcount;
  {---------------------------------------------}
  fillchar(ts[1],length(ts),#32);
  for i:=1 to 4096 do
    if i mod 13 =0 then ts[i]:=#0;
  start2:=gettickcount;
  for j:=0 to 255 do
    StrReplaceChar_(ts,#0,'A');
  end2:=gettickcount;
  memo1.lines.add('---------------------');
  memo1.lines.add(inttostr(end1-start1)+' 1.lauf');
  memo1.lines.add('---------------------');
  memo1.lines.add(inttostr(end2-start2)+' 2.lauf');
end;
Ja ich weiß, Zeitmessung mit tickcount sind alles andere als genau, (ich hab die bessere Funktion nicht mehr gefunden) aber da der Fehler bei beiden Versionen auftritt ignorier ich ihn einfach.
Irgendwie drängt sich mir der Schluß auf, daß beide Varianten gleich schnell sind.

Gruß
K-H

himitsu 17. Feb 2010 10:15

Re: #0 perfomanceschonend aus String entfernen
 
Erstmal das:
sonst würde ja schon beim ersten Durchlauf alles erstetzt und die letzten 255 Mal passiert nicht mehr viel.


Und dann:
Tja, da siehst du mal, wie gut der Compiler optimiert. :stupid:

p80286 17. Feb 2010 11:24

Re: #0 perfomanceschonend aus String entfernen
 
Zitat:

Zitat von himitsu
Erstmal das:
sonst würde ja schon beim ersten Durchlauf alles erstetzt und die letzten 255 Mal passiert nicht mehr viel.

Äh, willst Du mich auf den Arm nehmen? die Schleife über den String durchläuft er trotzdem, nur die eigentliche Ersetzen-Funktion fällt weg, und darum ging es Doch?

Gruß
K-H

himitsu 17. Feb 2010 11:32

Re: #0 perfomanceschonend aus String entfernen
 
:oops: Ups, ich glaub ich war vorhin so sehr in Klassenprozeduren vertieft, daß ich das Function glatt übersah. :shock:

Zitat:

Zitat von himitsu
Tja, da siehst du mal, wie gut der Compiler optimiert. :stupid:

Nja, dann bleibt immernoch das übrig. :angel:


Ach ja, das Andere wäre z.B. QueryPerformanceCounter gewesen,
aber auf die ~16 Millisekunden kommt es bei den "langen" Messzeiten wirklich nicht an.

p80286 17. Feb 2010 12:35

Re: #0 perfomanceschonend aus String entfernen
 
Mann könnte annehmen, daß heute Freitag ist.

???????????????????????????????????????????????

Gruß
K-H


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:01 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