Autor Beitrag
BenBE
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: So 29.08.04 19:52 
Ich hab gestern mal ein seltsames Phänomen festgestellt, dass ich mir nicht so richtig erklären kann:

Ich hab zwei Routinen geschrieben, die beide das gleiche machen: Prüfen ob eine Zahl prim ist. Zur

ausblenden volle Höhe 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:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
Function IsPrime(Number: DWORD): Boolean;
Asm
  PUSH  EBX
  PUSH   ESI
  XOR    EBX, EBX
  MOV    ESI, EAX
  CMP    ESI, $00000002
  JB    @@IsPrime_NoPrime
  JZ    @@IsPrime_Prime

  TEST  ESI, $00000001
  JZ    @@IsPrime_NoPrime
  
  MOV    ECX, $03
  JMP    @@IsPrime_CheckIfLess

@@IsPrime_Loop:
  MOV    EAX, ESI
  XOR    EDX, EDX
  DIV   ECX
  TEST  EDX, EDX
  JZ    @@IsPrime_NoPrime

  ADD   ECX, $02

@@IsPrime_CheckIfLess:
  MOV    EAX, ECX
  MUL    ECX
  CMP    EAX, ESI
  JA    @@IsPrime_Loop
  JZ    @@IsPrime_NoPrime
  
@@IsPrime_Prime:
  MOV    BL, $01
@@IsPrime_NoPrime:
  MOV    EAX, EBX
@@Ret:
  POP   ESI
  POP   EBX
End;

Function IsPrime2(Number: DWORD): Boolean;
Var
  Teiler: DWORD;
Begin
  Result := False;
  If Number < 2 Then
    Exit
  Else If Number = 2 Then
  Begin
    Result := True;
    Exit;
  End;

  If Number Mod 2 = 0 Then
    Exit;

  Teiler := 3;
  While Teiler * Teiler <= Number Do
  Begin
    If Teiler * Teiler <= Number Then
      Exit;

    If Number Mod Teiler = 0 Then
      Exit;

    Inc(Teiler, 2);
  End;

  Result := True;
End;

Procedure TForm1.TestClick(Sender: TObject);
Var
  T1, T2, T3, T4, T5: Int64;
  X, Y, Z: DWORD;
  B: Boolean;
Const
  NumTests = 500;
  ScaleBy = 50000;
  ScaleByY = 25;
  MaxNumber = 50000000;
  PerPixel = $7F;
Begin
  QueryPerformanceFrequency(T1);
  Image1.Height := 500;
  Image1.Width := 1 + MaxNumber Div ScaleBy;
  Image1.Picture.Bitmap.Height := Image1.Height;
  Image1.Picture.Bitmap.Width := Image1.Width;
  Image1.Picture.Bitmap.Canvas.Brush.Color := clBlack;
  Image1.Picture.Bitmap.Canvas.FillRect(Image1.Picture.Bitmap.Canvas.ClipRect);
  QueryPerformanceCounter(T2);
  QueryPerformanceCounter(T4);
  T4 := T4 - T2;
  With Image1.Picture.Bitmap, Canvas Do
  Begin
    T5 := 0;
    For Y := 1 To MaxNumber Do
    Begin
      B := True;
      QueryPerformanceCounter(T2);
      For X := 1 To NumTests Do
        B := IsPrime(Y) And B;
      QueryPerformanceCounter(T3);

      T3 := T3 - T2 - T4;
      If T3 < NumTests Then
        T3 := NumTests;
      T5 := T5 + T3;

      X := Height - Round(ScaleByY * Ln(T3 / NumTests)) - 1;
      Z := Pixels[Y Div ScaleBy, X];
      If Z and $000000FF < $100-PerPixel Then
      Z := Z +PerPixel;
      Pixels[Y Div ScaleBy, X] := Z;

      If Y Mod ScaleBy = 0 Then
      Begin
//        X := Height - Round(ScaleByY * Ln(T5 / NumTests / ScaleBy)) - 1;
//        Pixels[Y Div ScaleBy, X] := Pixels[Y Div ScaleBy, X] Or $000000FF;
//        T5 := 0;
        Update;
      End;
    End;
    T5 := 0;
    For Y := 1 To MaxNumber Do
    Begin
      B := True;
      QueryPerformanceCounter(T2);
      For X := 1 To NumTests Do
        B := IsPrime2(Y) And B;
      QueryPerformanceCounter(T3);
      T3 := T3 - T2 - T4;
      If T3 < NumTests Then
        T3 := NumTests;
      T5 := T5 + T3;

      X := Height - Round(ScaleByY * Ln(T3 / NumTests)) - 1;
      Z := Pixels[Y Div ScaleBy, X];
      If Z and $0000FF00 < $100 * ($100-PerPixel) Then
      Z := Z +$100*PerPixel;
      Pixels[Y Div ScaleBy, X] := Z;

      If Y Mod ScaleBy = 0 Then
      Begin
//                X := Height - Round(ScaleByY * Ln(T5 / NumTests / ScaleBy)) - 1;
//                Pixels[Y Div ScaleBy, X] := Pixels[Y Div ScaleBy, X] Or $0000FF00;
//                T5 := 0;
        Update;
      End;
    End;
  End;
End;


Dabei hab ich festgestellt, dass wenn man sich jede einzelne Messung anzeigen lässt man den Eindruck hat, als ob ASM LANGSAMER wäre als die Delphi-Prozedur (aktueller Quelltext). Entfernt man aber die Kommentare über den Beiden "Update;"-Zeilen und lässt sich den Durchschnitt anzeigen, so ist wie zu erwarten die ASM Routine schneller. Auch fällt auf, dass bei der Einzelpunkt-Anzeige die ASM-Routine wesentlich breiter streut, OBWOHL sie bis auf 2 Befehle weniger genau den gleichen Quelltext beinhaltet.

Hat jemand eine Idee, woran dies liegen könnte?

Achso: NumTests evtl. Anpassen, da es sonst zu Fehlern wegen 0-Taktzyklen-Messungen kommen kann.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
.Chef
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1112



BeitragVerfasst: Mo 30.08.04 10:34 
Ein Optimierungsproblem, und ich habe grad kein Delphi zur Hand. :(
Schau ich mir heut abend aber mal genauer an, wenn ich wieder an meinem Rechner sitze.

Die Streuung lässt sich vielleicht mit unterschiedlicher Subroutinenverwaltung erklären. Es könnte ja sein, dass du das weniger optimal gelöst hast als der Compiler.

_________________
Die Antworten auf die 5 häufigsten Fragen:
1. Copy(), Pos(), Length() --- 2. DoubleBuffered:=True; --- 3. Application.ProcessMessages bzw. TThread --- 4. ShellExecute() --- 5. Keine Vergleiche von Real-Typen mit "="!
BenBE Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Mo 30.08.04 21:00 
Naja, kann ich mir eher weniger Vorstellen, da der Source nahezu 1:1 vom CPU-Window übernommen wurde und dann zwei Dinge noch optimiert wurden:
1. Ein CMP-Befehl bei der Bereichsprüfung der Schleife entfernt
2. der JZ Befehl aus Zeile 31 wurde hinter den JA-Befehl gesetzt, um die Schleife schneller zu machen.

Das einzige Problem könnte ein winziger Bug in Zeile 29 gewesen sein, wo es CMP ESI, EAX anstatt CMP EAX, ESI heißén muss.

Hab aber andere Unregelmäßigkeiten entdeckt:
1. Wenn man mehrmals (auch mit REALTIME_CLASS) QueryPerformanceCounter hintereinander aufruft, so können sich die Werte um bis zu 1% Unterscheiden.
2. Bei sehr geringen Messzeiten ist die Messungenauigkeit selbst bei 1,4 Mrd Messzyklen (auf meinem AMD) sehr groß (Schwankungen beim Testprogramm um nahezu 5%! sowie eine breite Streuung. Die Messung der gleichen Routine kann sehr unterschiedliche Ergebnisse liefern (auch REALTIME_CLASS).
3. Auf mancher Hardware liefert die Messroutine IMMER 0 zurück, selbst wenn man 500000 Tests (NumTests für IP4 bei 1,6 GHz und nach meinen Messungen ca. 400\1400013000 Messzyklen) durchführen lässt und die Messfrequenz mit 3,5 Mio von QueryPerformanceFrequency angegeben wird.

_________________
Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.