Entwickler-Ecke

Algorithmen, Optimierung und Assembler - Performance Schwankungen


kamikaze - Sa 24.09.05 21:31
Titel: Performance Schwankungen
hab ne sequenz wo ich die Zeit berechne, in ein array speichere und dann den durchschnitt bilde...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
var
  start,stop,i,j,anz: Integer;
  S, str: String;
  fs: Tfilestream;
  b: array of Byte;
  arr: TBits;
  Zeit: array of Integer;

...
repeat
     start := Gettickcount;
     S := str;
     for i := 0 to 32767 do
      begin
       fs.Read(b[i], SizeOf(byte));
       S := S + BytetoBin(b[i]);
      end;
     for i := 0 to (length(S) div anz) - 1 do
      arr[BintoDec(PChar(COPY(S,i*anz+1,anz)))] := true;
     str := COPY(S,length(S) - (length(S) - (length(S) div anz)*anz)+1,length(S) - (length(S) div anz)*anz);
     stop := Gettickcount;
     Zeit[j] := stop-start;
     Inc(j);
until fs.Size = fs.Position;
...


function TForm1.ByteToBin(Value: Byte): String;
Var
    B: PByte;
Begin
    Value := Value And $FF;
    Result := StringOfChar('0'8);
    B := @Byte(Result[8]);
    While Value <> 0 Do
    Begin
        Inc(B^, Value And 1);
        Value := Value Shr 1;
        Dec(B);
    End;
End;

function TForm1.BintoDec(Value: PChar): Integer;
begin  
  Result := 0;  
  while Value^ <> #0 do  
  begin  
    Result := Result shl 1;  
    Result := Result or Ord(Value^ = '1');  
    Inc(Value);  
  end;  
end;

for i := 0 to high(Zeit) do
   j := j + Zeit[i];
ListBox1.Items.Add(FloattoStr(j/high(Zeit)));


dabei erhalte ich bei der gleichen Datei als Durchschnittszeiten zwischen 52 und 3190 Millisekunden, das ist ein extremer Unterschied, woran kann der liegen??


uall@ogc - So 25.09.05 14:34

hi startest du das programm zwischendruch neu? oder läßt du das einmal durchalufen wo es lange dauert und bei jedem weiteren durchlauf (ohen programm beenden) gehst schneller

das liegt dann vielleicht daran das du nen string immer vergrößerst, beim 2. mal durchlaufen hat delphi dann schon einen großen speicehrbereich frei für den string, dann muss kein neuer speicher allociert werden

oder windows hat die datei die du einließt noch irgendwie im speicher etc. :?:


GTA-Place - So 25.09.05 14:43

Da du J nicht zurückstetzt (außer du startest dazwischen neu), wird die Zahl größer.

Zuerst bekommst du 59 / high();, dann 590 / high();, dann 5900 / high();, ...;


uall@ogc - So 25.09.05 14:47

hatte den quelltext nicht getestet, aber sowas sollte man doch merken wenn die zeit um einen bestimmten faktor ansteigt und nich wie von dir gesagt schwangt.


kamikaze - Mo 26.09.05 12:50

j wird am anfang immer nochma neu gesetzt, hatte ich aus Versehen weggelassen.

kann sein, dass es schneller wird, wenn man es öffter macht ohne es neu zu starten, aber trotzdem kommen zwischendurch immer wieder hohe werte.

So war meine Durchschnittszeiten bei einer Datei:
3192
74
2078
620
403
52
52
768
1304


Hab zwischendurch nicht neu gestartet.
hab mir auch ma die Zeiten für die einzelnen Durchläufe angeguckt, da schwankts sogar von 50 bis 17000 Msek.


Delete - Mo 26.09.05 12:59

Was laufen noch für Programme im Hintergrund? Desweiteren ist GetTickCount sehr, sehr ungenau und dafür eigentlich nicht zu gebrauchen, da es auch die Zeit mit mißt, die dein Thread nicht aktiv ist und daraufwartet wieder CPU Zeit zugeteilt zu bekommen. Wenn dazwischen jetzt andere Threads mit gleicher Priorität liegen, dann kann es schon mal etwas dauern bis er wieder CPU Zeit zugewiesen bekommt. Besser wäre auf alle Fälle Suche im MSDN GETTHREADTIMES.


Gausi - Mo 26.09.05 13:07

Ein weiterer Punkt dürfte der Platten-Cache sein, oder? Wenn du ne kleine Datei öffnest, und dann Byteweise die Zeichen einliest, braucht das wesentlich mehr Zeit, wenn die Datei echt gelesen wird, als wenn sie noch im Cache liegt.


kamikaze - Mi 28.09.05 14:18

Das Problem liegt nich in der Zeitmessung, sondern, dass es mal <10 sek und mal > 5 min braucht um meine Testdatei (ca. 2,82 MB) einzulesen, die Zeitmessung ist nur zur Veanschaulichung.


Keine Ahnung ob das am plattencache liegt, wenns daran liegt, wie kann man dafür sorgen, dass es immer möglichst schnell geht, oder die datei am Anfang im cache ist?


kamikaze - Mi 28.09.05 22:25

Hab den Übeltäter gefunden:


Delphi-Quelltext
1:
2:
3:
4:
5:
for i := 0 to 32767 do  
      begin  
       fs.Read(b[i], SizeOf(byte));  
       S := S + BytetoBin(b[i]);  
      end;


habs modifiziert:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
fs.Read(b[1], 32768);
       S := S + BytetoBin(b,32768);


function TForm1.ByteToBin(Value: String; range: integer): String;
Var
    B: PByte;
    i: Integer;
    st: String;
    c: ^byte;
Begin
  Result := '';
  c := @Byte(Value[1]);
  for i := 1 to range do
   begin
    st := StringOfChar('0'8);
    B := @Byte(st[8]);
    While c^ <> 0 Do
      Begin
        Inc(B^, c^ And 1);
        c^ := c^ Shr 1;
        Dec(B);
      End;
    Result := Result + st;
    c := pointer(cardinal(c)+1);
   end;
End;



läuft schon wesentlich stabiler, aber nach mehrmaliger Ausführung mit derselben Datei werden die Zeiten wieder groß, keine Ahnung warum, naja vielleicht klärt sich das noch irgendwann...


uall@ogc - Do 29.09.05 10:22

für nen

String := String+ "irgendwas"

wird immer neuer speicher allociert, am besten berechnest du vorher wieviel du brauchst


Horst_H - Do 29.09.05 10:30

Hallo,

hast Du Dir schon mal die Speicherauslastung Deines Programms waehrend der Ausfuehrung angesehen?
Funktionen,die eine String als Rueckgabewert haben, sind bei Dauereinsatz immer Speicherfresser, da dieser String nicht aus dem Speicher verschwindet.
Wie dem auch sei ich habe ein anderes Problem bei mir festgestellt. Die Zuweisung eines riesigen Strings in ein MemoFeld mit Wordwrap = true ist der GAU.
Also die 2.8 MB sind nach Minuten erst erschienen(98% CPU).

Ein Vorschlag von mir.Einsatz einer Prozedur mit var Parameter und Benutzung von setlength, wenn die Laenge des endgueltigen Strings bekannt ist.
DIe Laufzeit auf meinem Rechner 0.36 Sekunden ( bei string[4] 0.4 s)

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  Tnibble = array[0..15of String[7];
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    procedure ByteToBin(const Value: String; range: integer;var outStr: String);
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation
{$R *.dfm}

procedure TForm1.ByteToBin(const Value: String; range: integer;var outStr: String);
const
   nibble : Tnibble  =
            ('0000','0001','0010','0011',
             '0100','0101','0110','0111',
             '1000','1001','1010','1011',
             '1100','1101','1110','1111');

Var
  t0,t1: cardinal;
  i: Integer;
  PosOut,c : ^byte;
Begin
  self.Memo1.Lines.Add('Jetzt geht''''s los ');
  t0 := Gettickcount;
  setLength(outStr,8*range);
  Posout := Addr(outStr[1]);
  c := Addr(Value[1]);
  for i := 1 to range do
    begin
    move(nibble[c^ shr 4][1],PosOut^,4);
    inc(PosOut,4);
    move(nibble[c^ AND 15][1],PosOut^,4);
    inc(PosOut,4);
    inc(c);
   end;
  t1 := Gettickcount;
  self.Memo1.Lines.Add('Fertig ');
  self.Memo1.Lines.Add(Format(' %7.4f Sekunden',[(t1-t0)/1000]));
End;


procedure TForm1.Button1Click(Sender: TObject);
var
  i           : integer;
  strTest,
  strErgebnis : String;
begin
  memo1.WordWrap := false;
  setlength(strTest,2820000);
  strTest:= StringOfChar(chr(64+16+4+1),length(strTest));//'U'=01010101
//  self.Memo1.Lines.Add(strTest);
  self.Memo1.Lines.Add(Format(' Anzahl Zeichen Eingabe %6d',[length(strTest)]));

  self.ByteToBin(strTest,length(strTest),strErgebnis);

  self.Memo1.Lines.Add(Format(' Anzahl Zeichen Ergebnis %6d',[length(strErgebnis)]));
//  self.Memo1.Lines.Add(strErgebnis);
end;

end.


Gruss Horst


kamikaze - Do 29.09.05 14:23

Danke, die Variante ist wirklich extrem gut!

vielleicht wisst ihr noch was, wie ich diesen Teil noch bissl verbessern kann:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
for i := 0 to (length(S) div anz) - 1 do
      arr[BintoDec(PChar(COPY(S,i*anz+1,anz)))] := true;


function TForm1.BintoDec(Value: PChar): Integer;
begin
  Result := 0;
  while Value^ <> #0 do
  begin
    Result := Result shl 1;
    Result := Result or Ord(Value^ = '1');
    Inc(Value);
  end;
end;



die Funktion läuft zwar ganz gut, aber vielleicht kann man noch was verbessern, wegen COPY und so


Horst_H - Do 29.09.05 18:25

Hallo,

ich weiss nicht, was Du da vorhast.
Aus einem Binaerstring erzeugst Du die Position in einem BitFeld ??

Durch pChar(COPY(strErgebnis,i*anz+1,anz)) erzeugst Du unnoetige Kopien, wenn Du Dir sicher sein kannst nicht ueber das Ende hinaus zu lesen.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
tChar   = ^Char;//Bei pChar hat er immer das Zeichen und nicht die Adresse uebergeben
...
anz := 32;

for i := 0 to (length(StrErgebnis) div anz) - 1 do
   j:=BintoDec(Addr(StrErgebnis[i*anz+1]),anz);

function TForm1.BintoDec(Value : tChar;Anz: cardinal): Integer;
begin
  Result := 0;
  Anz := cardinal(Value)+Anz;
  while cardinal(Value) < Anz do
    begin
    Result := Result shl 1;
    Result := Result or Ord(Value^ = '1');
    Inc(Value);
    end;
end;


Anz sollte also maximal 32 sein!
So ist etwa 3.5..4 mal schneller (.2..0.23 Sekunden fuer einen 80 Mbyte strErgebnis statt vorher 0.79..083 Sekunden) uups 400 Mbyte/s haette ich nicht erwartet.

Gruss Horst