Code schneller machen, ..

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1629
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Code schneller machen, ..

Beitrag von corpsman »

Guten Morgen allerseits,

ich hab mir hier nen kleinen Hex-Editor gebastelt. Dieser Lädt Dateien in einen TMemorystream und daraus dann in ein Synedit. Das funktioniert auch alles wunderbar. Wenn ich nun eine 20MB Datei lade dauert das ganze erzeugen allerdings ca. 10-15 Sekunden, ursprünglich hatte es 1min gedauert, habe da also schon einiges "optimiert".

Die Frage nun sieht einer von Euch evtl. noch Stellen an denen ich etwas schneller werden kann ?

Code: Alles auswählen

 
Procedure TForm1.StreamToSynEdit(Const Stream: TMemoryStream;
  Const SynEdit: TSynEdit);
Const
  ToHex: Array[0..15] Of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
Var
  s, s2: String;
  j, i, k: integer;
  b: Byte;
  p: Integer;
Begin
  // Das ist schon "Schnell" aber Noch schneller wäre Besser *g*.
  SynEdit.Lines.BeginUpdate;
  SynEdit.Lines.Clear;
  stream.Position := 0;
  setlength(s, 10 + 3 * 16); // Den Kompletten String im Vorraus Allokieren, spart Zeit
  s[8] := '0'; // Ist immer 0
  s[9] := ':';
  For j := 10 To 10 + 3 * 16 Do // Alles zu "Null" machen
    s[j] := ' ';
  For i := 0 To stream.Size Div 16 Do Begin
    p := stream.Position;
    P := p Shr 4;
    For j := 7 Downto 1 Do Begin
      s[j] := ToHex[P And $F];
      P := p Shr 4;
    End;
    For j := 0 To 15 Do Begin
      If stream.read(b, sizeof(b)) <> 0 Then Begin
        s[10 + j * 3 + 1] := ToHex[(B Shr 4) And $F];
        s[10 + j * 3 + 2] := ToHex[B And $F];
      End
      Else Begin
        // Den Rest des Strings wieder Löschen
        For k := j To 15 Do Begin
          s[10 + k * 3 + 1] := ' ';
          s[10 + k * 3 + 2] := ' ';
        End;
        break;
      End;
    End;
    SynEdit.Lines.Add(s);
  End;
  SynEdit.Lines.EndUpdate;
End;                     
 
Gruß Corpsman
--
Just try it

Benutzeravatar
theo
Beiträge: 10899
Registriert: Mo 11. Sep 2006, 19:01

Re: Code schneller machen, ..

Beitrag von theo »

Sicher kann man da ein bisschen rumschrauben, aber das grosse Problem ist SynEdit.Lines.Add(s);
Viel kann man da afaik so nicht machen.
Ich würde dafür ein eigenes Control basteln, welches nur den sichtbaren Teil lädt.
Ich hab mal probiert, das Ganze in eine StringList statt Synedit zu laden, das geht ca 7 x schneller.
Davon dann nur die aktuell angezeigten Lines ins Control laden.
Im Prinzip kann man auch noch die StringList weglassen und nur so viel vom Stream umwandeln, was gerade benötigt wird.
Das geht dann ratz-fatz.

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1629
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Code schneller machen, ..

Beitrag von corpsman »

Das das Synedit der Flaschenhals ist, dachte ich mir schon fast, werde mal deinen Ansatz mit der Stringliste Testen, danke .
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Code schneller machen, ..

Beitrag von mse »

Alle Zuweisungen der Art

Code: Alles auswählen

 
 s[irgendetwas] :=
 
rufen fpc_ansistr_Unique() auf. Schneller ist

Code: Alles auswählen

 
 pchar(pointer(s))[irgendetwas] := //index null-basierend!
 
nach

Code: Alles auswählen

 
    SynEdit.Lines.Add(s);
 
muss

Code: Alles auswählen

 
    uniquestring(s);
 
aufgerufen werden, damit eine echte Kopie der Daten angelegt wird. Daher ist es vermutlich günstiger, mit ShortString als Puffer zu arbeiten.

Code: Alles auswählen

 
var
  s: string[10 + 3 * 16];
 
Dann ist

Code: Alles auswählen

 
      If stream.read(b, sizeof(b)) <> 0 Then Begin
 
eine teure Sache. tmemorystream liefert einen pointer zum Datenblock, welcher zum Zugriff verwendet werden kann. Etwa so:

Code: Alles auswählen

 
var
 datapo,endpo: pbyte;
begin
 datapo:= stream.memory;
 endpo:= datapo;
 inc(endpo,(stream.size div bytesprozeile) * bytesprozeile); //vollständige zeilen
 while datapo < endpo do begin
  //wandle die daten
  inc(datapo);
 end;
 endpo:= datapo + stream.size mod bytesprozeile;
 while datapo < endpo do begin
   //wandle die die letzte unvollständige zeile
  inc(datapo);
 end;
 
Aber wenn das Problem, wie Theo sagt, an synedit liegt, ist in der Wandlung ja keine Optimierung notwendig.

Martin

Benutzeravatar
theo
Beiträge: 10899
Registriert: Mo 11. Sep 2006, 19:01

Re: Code schneller machen, ..

Beitrag von theo »

mse hat geschrieben: Aber wenn das Problem, wie Theo sagt, an synedit liegt, ist in der Wandlung ja keine Optimierung notwendig.
Sicherlich lässt sich dieser Code noch drastisch verbessern, nur ändert das nichts an der Tatsache, dass du damit höchstens 5-10% schneller wirst.
Der Engpass liegt im Moment beim Befüllen des Synedit.
Ich glaube nicht, dass corpsman mit einer Verbesserung von 20 sec. auf 19 sec. zufrieden ist.

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Code schneller machen, ..

Beitrag von martin_frb »

Versuche erst alles in die Stringlist, und dann

SynEdit.Lines.Assign(list);

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1629
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Code schneller machen, ..

Beitrag von corpsman »

*g*

schön zu lesen wenn Fachmänner meinen Code zerlegen.

Das Strings teuer sind wusste ich, deswegen hatte ich es ja mit der "Voralokierung" versucht.

Das einbaun der Stringlist, bringt bei mir eher eine Verschlechterung, als eine Verbesserung (zumindest wenn ich es mittels Gettickcount vermesse ).

Wenn ich das "Gehacke" mit den Strings richtig lese, kann ich mir das Sparen wenn ich s als

Code: Alles auswählen

var
  s: string[10 + 3 * 16];
Deklariere ?, oder brauche ich dann auch noch diesen Pointer zugriff und das uniquestring ? ( so wie ich das verstanden habe wird das dann überflüssig ).
Ich glaube nicht, dass corpsman mit einer Verbesserung von 20 sec. auf 19 sec. zufrieden ist.
Ansich ists mir wurscht wie schnell es geht, ich bin ja schon froh, dass es überhaupt geht. Mir gehts hier mehr um den Lerneffekt bei meinem Send Get File Projekt konnte ich damals durch ansich kleine Verbesserungen ( weglassen des Stream.position... ) eine Geschwindigkeitssteigerung im Bereich Faktor 100 bis 1000 erreichen.

Aktuell braucht mein Code zum Laden ca. 4,5 Sekunden, das liegt durchaus im Bereich des Akzeptablen.

Hatte auch schon mal mit dem Gedanken Gespielt den kompletten String zu allokieren und dann von Hand zu befüllen ( das dauerte ca. 2,5 Sek, jedoch noch ohne den Stream Zugriff).

Gruß
Corpsman
--
Just try it

carli
Beiträge: 657
Registriert: Sa 9. Jan 2010, 17:32
OS, Lazarus, FPC: Linux 2.6.x, SVN-Lazarus, FPC 2.4.0-2
CPU-Target: 64Bit

Re: Code schneller machen, ..

Beitrag von carli »

Baue deine eigene Komponente.
Oder fülle nur die gerade sichtbaren Zeichen ins SynEdit und ersetze den Scrollbalken durch einen programmierbaren Scrollbalken, der bei onChange den Inhalt des SynEdit auswechselt.

martin_frb
Beiträge: 586
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Code schneller machen, ..

Beitrag von martin_frb »

corpsman hat geschrieben:*g*
Das einbaun der Stringlist, bringt bei mir eher eine Verschlechterung, als eine Verbesserung (zumindest wenn ich es mittels Gettickcount vermesse ).
Interessant.

Die IDE nutzt SynEdit.Lines.Assign. Und "Assign" ist in SynEdit *wesentlich* besser optimiert als "add".

Probiere mal bei Stringlist, mittels "capacity" speicher zu reservieren.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Code schneller machen, ..

Beitrag von mse »

corpsman hat geschrieben: Wenn ich das "Gehacke" mit den Strings richtig lese, kann ich mir das Sparen wenn ich s als

Code: Alles auswählen

var
  s: string[10 + 3 * 16];
Deklariere ?, oder brauche ich dann auch noch diesen Pointer zugriff und das uniquestring ? ( so wie ich das verstanden habe wird das dann überflüssig ).
Richtig, das wird dann überflüssig. Was du unbedingt vermeiden solltest, ist das "stream.read(b, sizeof(b))" für jedes Byte.
Wenn du weiter optimieren willst, lassen sich im Bereich für die vollständigen Zeilen mit einer grösseren Tabelle jeweils ganze Bytes verarbeiten:

Code: Alles auswählen

 
 hextable: array[byte] of array[0..1] of char =
 (
  ('0','0'),('0','1'),('0','2'),('0','3'),('0','4'),('0','5'),('0','6'),('0','7'),
  ('0','8'),('0','9'),('0','A'),('0','B'),('0','C'),('0','D'),('0','E'),('0','F'),
 
  ('1','0'),('1','1'),('1','2'),('1','3'),('1','4'),('1','5'),('1','6'),('1','7'),
  ('1','8'),('1','9'),('1','A'),('1','B'),('1','C'),('1','D'),('1','E'),('1','F'),
....
 
Dabei muss dem Compiler bei der Zuweisung möglicherweise etwas auf die Sprünge geholfen werden, damit er alle 16Bit in einer Operation kopiert. Wenn du willst, können wir die Übung ja mal durchziehen. :-)

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1629
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Code schneller machen, ..

Beitrag von corpsman »

So ab jetzt arbeite ich unter Linux ( bis zum Montag *g* )

Unter Linux dauert es anstatt der 4,5 Sek ca. 6.2 Sek.

Das Nutzen der Stringlist in Verbindung mit SetCapacity bringt eine Verbesserung auf 5,7 Sek also ca. 8 Prozent.

@mse

Muss zugeben so ganz kapiert habe ich es nicht was du meinst, aber zum Experemtieren bin ich bereit, schließlich will ich ja was lernen *g*.

[Edit]

So die sache mit dem Shortstring verbessert noch mal um 0,8 auf 5,1 Sek *g*
Zuletzt geändert von corpsman am Fr 12. Apr 2013, 16:54, insgesamt 1-mal geändert.
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Code schneller machen, ..

Beitrag von mse »

Na dann zeig mal was du gemacht hast.

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1629
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: Code schneller machen, ..

Beitrag von corpsman »

Bisher siehts so aus :

Code: Alles auswählen

 
Procedure TForm1.StreamToSynEdit(Const Stream: TMemoryStream;
  Const SynEdit: TSynEdit);
Const
  ToHex: Array[0..15] Of Char = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
Var
  s: String;
  j, i, k: integer;
  b: Byte;
  p, p2: Integer;
  sl: TStringlist;
  t: Dword;
Begin
  t := gettickcount;
  sl := TStringList.create;
  sl.BeginUpdate;
  sl.Capacity := (stream.Size Div 16) * (LineLen + CRTLen);
  SynEdit.Lines.BeginUpdate;
  SynEdit.Lines.Clear;
  stream.Position := 0;
  setlength(s, 10 + 3 * 16);
  s[8] := '0'; // Ist immer 0
  s[9] := ':';
  For j := 10 To 10 + 3 * 16 Do // Alles zu "Null" machen
    s[j] := ' ';
  b := 0;
  p2 := 0;
  For i := 0 To stream.Size Div 16 Do Begin
    P := p2 Shr 4;
    For j := 7 Downto 1 Do Begin
      s[j] := ToHex[P And $F];
      P := p Shr 4;
    End;
    For j := 0 To 15 Do Begin
      If stream.read(b, sizeof(b)) <> 0 Then Begin
        s[10 + j * 3 + 1] := ToHex[(B Shr 4) And $F];
        s[10 + j * 3 + 2] := ToHex[B And $F];
      End
      Else Begin
        // Den Rest des Strings wieder Löschen
        For k := j To 15 Do Begin
          s[10 + k * 3 + 1] := ' ';
          s[10 + k * 3 + 2] := ' ';
        End;
        break;
      End;
    End;
    sl.Add(s);
    inc(p2, 16);
  End;
  sl.EndUpdate;
  SynEdit.Lines.Assign(sl);
  sl.free;
  SynEdit.Lines.EndUpdate;
  t := GetTickCount - t;
  showmessage(inttostr(t Div 1000) + ',' + inttostr(t Mod 1000));
End;
 
 
[Edit]
Die Shortstring Geschichte musste ich nun leider doch wieder Raus nehmen, aus unerfindlichen Gründen wurden nur die ersten 13 Hex Zahlen eingetragen 14-16 fehlten in allen Zeilen.
--
Just try it

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Code schneller machen, ..

Beitrag von mse »

Zuerst muss " If stream.read(b, sizeof(b)) <> 0 Then Begin" weg, da wird für jedes Byte eine Riesenmaschine in Gang gesetzt. Gleichzeitig würde ich die Datenaufbereitung in zwei Bereiche aufteilen, einen ersten für die vollständigen Zeilen und einen zweiten für die letzte angebrochene Zeile. Machen wir mal den ersten Teil:

Code: Alles auswählen

 
var
   datapo: pbyte;
 ...
  datapo:= stream.memory;
 ...
  For i := 0 To stream.Size Div 16 - 1 Do Begin //die vollständigen zeilen
...
    For j := 0 To 15 Do Begin
//      If stream.read(b, sizeof(b)) <> 0 Then Begin
        b:= datapo^;
        inc(datapo);
        s[10 + j * 3] := ToHex[(B Shr 4) And $F];
        s[10 + j * 3 + 1] := ToHex[B And $F];
      End;
      { //der Rest kommt später
      Else Begin
 

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Code schneller machen, ..

Beitrag von mse »

corpsman hat geschrieben: [Edit]
Die Shortstring Geschichte musste ich nun leider doch wieder Raus nehmen, aus unerfindlichen Gründen wurden nur die ersten 13 Hex Zahlen eingetragen 14-16 fehlten in allen Zeilen.
Na, na, so geht's nicht! ;-)
Edit:
Aua!

Code: Alles auswählen

 
    For j := 6 Downto 0 Do Begin <<<<<<-------
      s[j] := ToHex[P And $F];
      P := p Shr 4;
    End;
 
Auf s[0] ist das Längenbyte!

Antworten