Autor Beitrag
Phantom1
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 390



BeitragVerfasst: Mi 10.12.03 23:56 
Hier eine Unit mit der man eine ziemlich genaue Zeitmessung durchführen kann. Den Assembler-Code hatte ich irgendwann mal im Internet gefunden (Author/Quelle leider unbekannt), hab das ganze dann einfach in eine Klasse geschrieben, zur besseren übersicht. Der RDTSC (ReadTimeStampCounter) ist übrigens noch genauer als der QueryPerformaceCounter und GetTickCount.

Hier erstmal die Unit uCounter:

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:
Unit uCounter;

Interface

Uses Windows, SysUtils;

Type
  ECounter=Class(Exception);

  TCounter=Class
  Private
    mCPUFrequency : Int64;
    mStart        : Int64;
    mTimeCritical : Boolean;
    mPriorityClass: Cardinal;
    mPriority     : Cardinal;
    Function RDTSC: Int64;
    Function IsRDTSCPresent: Boolean;
    Procedure SetTimeCritical(active: Boolean);
    Procedure CalcCPUFrequency;
  Public
    Constructor Create(StartTimeCritical: Boolean = False);
    Procedure Start(TimeCritical: Boolean = False);
    Function Stop(StopTimeCritical: Boolean = True): Double;
    Function GetCPUFrequency: Double;
    Destructor Destroy; Override;
  End;

Implementation

Constructor TCounter.Create(StartTimeCritical: Boolean = False);
Begin
  Inherited Create;
  If Not IsRDTSCPresent Then
    Raise ECounter.Create('Time-Stamp Counter (RDTSC) ist nicht verfügbar!');
  mPriorityClass:=GetPriorityClass(GetCurrentProcess);
  mPriority:=GetThreadPriority(GetCurrentThread);
  CalcCPUFrequency;
  Start(StartTimeCritical);
End;

Function TCounter.RDTSC: Int64; Assembler;
asm
       DW    0310Fh
End;

Function TCounter.IsRDTSCPresent: Boolean;

  Function HasRDTSC: Boolean; Assembler;
  asm
         PUSH    EBX
         PUSHFD
         PUSHFD
         POP     EAX
         MOV     EDX,EAX
         XOR     EAX,0040000h
         PUSH    EAX
         POPFD
         PUSHFD
         POP     EAX
         XOR     EAX,EDX
         JZ      @@1
         PUSHFD
         POP     EAX
         MOV     EDX,EAX
         XOR     EAX,0200000h
         PUSH    EAX
         POPFD
         PUSHFD
         POP     EAX
         XOR     EAX,EDX
    @@1POPFD
         TEST    EAX,EAX
         JZ      @@2
         MOV     EAX,1
         DW      0A20Fh
         TEST    EDX,010h
        SETNZ   AL
    @@2POP     EBX
  End;

Begin
  Try
    Result:=HasRDTSC;
    If Result Then RDTSC;
  Except
    Result:=False;
  End;
End;

procedure TCounter.SetTimeCritical(active: Boolean);
begin
  mTimeCritical:=active;
  If active Then Begin
    SetPriorityClass(GetCurrentProcess, REALTIME_PRIORITY_CLASS);
    SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
  End Else Begin
    SetThreadPriority(GetCurrentThread, mPriority);
    SetPriorityClass(GetCurrentProcess, mPriorityClass);
  End;
end;

Procedure TCounter.Start(TimeCritical: Boolean = False);
Begin
  If TimeCritical And Not mTimeCritical Then
    SetTimeCritical(True);
  mStart:=RDTSC;
End;

Function TCounter.Stop(StopTimeCritical: Boolean=True): Double;
Begin
  Result:=(RDTSC-mStart)*1000/mCPUFrequency;
  If StopTimeCritical And mTimeCritical Then
    SetTimeCritical(False);
End;

Procedure TCounter.CalcCPUFrequency;
Var
  C, F, S1, E1, S2, E2: Int64;
Begin
  SetTimeCritical(True);
  QueryPerformanceFrequency(F);
  C:=F*2;
  QueryPerformanceCounter(S1);
  S2:=RDTSC;
  While C>0 Do Dec(C);
  QueryPerformanceCounter(E1);
  E2:=RDTSC;
  SetTimeCritical(False);

  mCPUFrequency:=Round((E2-S2)*F/(E1-S1));
End;

Function TCounter.GetCPUFrequency: Double;
Begin
  Result:=mCPUFrequency/1000000;
End;

Destructor TCounter.Destroy;
Begin
  If mTimeCritical Then
    SetTimeCritical(False);
  Inherited;
End;

End.


Sobald das Object erstellt wird (TCounter.Create), wird der erste Startzeitpunkt für die Messung gesetzt. Als parameter [StartTimeCritical] kann man angeben ob die Messung mit höchster Priorität (TimeCritical) gestartet werden soll. Oder man setzt den Startpunkt der Messung erst mit TCounter.Start (als Parameter kann man hierbei auch angeben ob die Messung mit höchster Priorität erfolgen soll).

Die Methode "Stop" liefert die gemessene Zeit zurück in millisekungen mit nachkommastellen. Mit dem parameter (StopTimeCritical) kann man angeben ob die höchste Priorität wieder auf normal zurückgesetzt werden soll (default). Man brauch den Parameter eigentlich nur wenn man mehrmals die Zeit mit höchster Priorität von Startzeitpunkt messen will.

Die Default-Einstellungen, misst die Zeit ohne erhöhter Prioität durch!

Hier ein simpler Beispielaufruf mit normaler Prioität:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TForm1.FormCreate(Sender: TObject);
Var Counter: TCounter;
begin
  Counter:=TCounter.Create;
  Try
    Counter.Start;
    // mach irgendwas... 
    Caption:=FloatToStr(Counter.Stop);
  Finally
    Counter.Free;
  End;
End;


Zuletzt bearbeitet von Phantom1 am Fr 12.12.03 19:13, insgesamt 3-mal bearbeitet
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Do 11.12.03 10:12 
Nur ein "Problem"... der Code berechnet die vergangene Zeit basierend auf der Anzahl der vergangenen CPU-Takte in der Zeit zwischen "Start" und "Stop". Da Windows aber mit preemtiven Multitaskting (bzw. korrekterweise Multithreading) arbeitet kann es durchaus der Fall sein, dass der Thread während dieser Zeitmessung die CPU entzogen bekommt und ein anderer Thread Rechenzeit zugeteilt bekommt. (-> die genaue Vorgehensweise kann man sich am besten in Luckies Thread-Tutorial anschaun...!) Um diesem Verhalten von Windows ein bisschen "vorzubeugen" kann man für die Zeit der Zeitmessung die Priorität des Prozesses kurzfristig auf TimeCritical hinaufsetzen (Wichtig - nachher sofort wieder heruntersetzen!)

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
Phantom1 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 390



BeitragVerfasst: Do 11.12.03 18:36 
Danke für den Tipp, habe den Code oben soeben aktualisiert.
Currywurst
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 50

Win XP Pro
D3 Pro, D5 Std, D6 Pers
BeitragVerfasst: Fr 12.12.03 06:53 
du solltest ihn nocheinmal aktualisieren, denn so war das sicher nich ganz gemeint :-)
Phantom1 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 390



BeitragVerfasst: Fr 12.12.03 09:57 
Currywurst hat folgendes geschrieben:
du solltest ihn nocheinmal aktualisieren, denn so war das sicher nich ganz gemeint :-)


Achja, dann sag mir doch bitte wie er es gemeint hat? Wenn du darauf hinaus willst, das auch die Berechnungen selbst (die gemessen werden sollen) auf TimeCritical laufen sollen, dann haben wir ein Problem, weil dann würde das ganze Betriebssystem für die dauer der Berechnungen blockiert sein und das ist ja nicht der sinn der sache. Das sollte dann jeder selbst entscheiden und hat mit meiner Unit eigentlich nix zu tun.
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Fr 12.12.03 10:34 
Doch... so ungefähr hab ich das gemeint...

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
Currywurst
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 50

Win XP Pro
D3 Pro, D5 Std, D6 Pers
BeitragVerfasst: Fr 12.12.03 13:57 
Zitat:

Wenn du darauf hinaus willst, das auch die Berechnungen selbst (die gemessen werden sollen) auf TimeCritical laufen sollen, dann haben wir ein Problem, weil dann würde das ganze Betriebssystem für die dauer der Berechnungen blockiert sein

doch, so meint ich es, und das die func eh nich so toll sprach aber schon wer anders an, jedenfalls, die priorität ändern nur zum auslesen des counter wertes macht wirklich _keinen_ sinn
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Fr 12.12.03 14:07 
Currywurst hat folgendes geschrieben:
jedenfalls, die priorität ändern nur zum auslesen des counter wertes macht wirklich _keinen_ sinn

Du meinst zum Emitteln der CPU-Frequenz? Oh doch.. auch hier ist das anheben der Priorität durchaus sinnvoll um einen möglichst exakten Wert zu erhalten..!

@Phantom1: mir ist aber gerade ein Fehler aufgefallen... du setzt bei TCounter.Start die Priorität hinauf - passt! Aber bei Stop hast du diesen Code:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Function TCounter.Stop: Double; 
Var PriorityClass, Priority: Cardinal; 
Begin 
  PriorityClass:=GetPriorityClass(GetCurrentProcess); 
  Priority:=GetThreadPriority(GetCurrentThread); 
  SetPriorityClass(GetCurrentProcess, REALTIME_PRIORITY_CLASS); 
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL); 

  Result:=(RDTSC-mStart)*1000/mCPUFrequency; 

  SetThreadPriority(GetCurrentThread, Priority); 
  SetPriorityClass(GetCurrentProcess, PriorityClass); 
End;

Hier wird die Priorität aber nicht wieder auf die normale herabgesetzt! Und du brauchst sie auch nicht nochmal auf TimeCritical anheben, denn dort ist sie ja schon seit dem Aufruf von Start. Also entweder lasst du alles auf den hohen Prioritäten und gibst nur die Zeit zurück und führst stattdessen einen Destruktor ein in dem die Priorität wieder auf den Standard heruntergesetzt wird, oder aber du setzt sie gleich in der Stop-Methode wieder herunter..!

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
Phantom1 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 390



BeitragVerfasst: Fr 12.12.03 14:20 
@Currywurst: wer sagt denn hier das die function/unit nicht so doll ist? das hat doch niemand hier gesagt! Die Priorität ändern beim auslesen des counter wertes macht durchaus sinn, desweiteren sollte jeder selber entscheiden ob sein programm auf timecritical laufen lässt. Mhhh mir ist aber gerade noch etwas dazu eingefallen, man könnte das mit einen optionalen parameter in meiner unit angeben, dann sollten auch alle zufrieden sein 8) ich werd mich gleich mal an die arbeit machen :wink:

@Motzi:

Ich glaub du hast da was übersehen, ich setzte die priorität bei TCounter.Start auf TimeCritical und anschließend auf den vorherigen wert wieder zurück (daher auch die beiden variablen PriorityClass und Priority)! Das gleiche bei TCounter.Stop !
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Fr 12.12.03 14:23 
Oh.. stimmt... damit hast du aber wieder dasselbe Problem wie schon oben beschrieben dass die Zeitmessung nicht stimmt weil der Scheduler deinem Prozess dazwischen die Rechenzeit entzogen hat... um hier einen möglichst exakten Wert zu erreichen sollte die gesamte Zeitmessung auf TimeCritical laufen...

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
Phantom1 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 390



BeitragVerfasst: Fr 12.12.03 14:27 
@Motzi: das empfehle ich aber nicht, da sonst das ganze betriebsystem blockiert wird, falls die berechnungen mehrere minuten dauern! Wie ich aber schon erwähnt habe werde ich einen optionalen parameter in meine unit mit einbauen, wo dann jeder selbst entscheiden kann.
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Fr 12.12.03 16:25 
Phantom1 hat folgendes geschrieben:
@Motzi: das empfehle ich aber nicht, da sonst das ganze betriebsystem blockiert wird, falls die berechnungen mehrere minuten dauern!

Ja, aber gerade für solche Zeitmessungen die mehrere Minuten dauern braucht man auch keine solche genauen Methoden..! Und ohne TimeCritical bekommt man mit dieser Methode eben ein vollkommen verfälschtes Ergebnis..!

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
UC-Chewie
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 531

WinXP
D5 Ent
BeitragVerfasst: Fr 12.12.03 16:45 
Eventuell sind diese Verfälschungen aber auch gewünscht.
Wenn man herausfinden will, wie lange eine bestimmte Berechnung in einem Programm dauern wird und die Berechnung zum Messen auf Time-Critical durchführt, weiß man immer noch nicht, wie lange sie im fertigen Programm brauchen wird, da dort der laufende Thread nicht der einzige ist, der zum Zuge kommt. Mit normaler Priorität kann man so hier den ungefähren tatsächlichen Wert rauskommen (bei durchschnittlicher Systemauslastung). Führt man nun viele Messungen aus und bildet den Durchschnitt, hat man die Zeit, die die Berechnung wirklich dauert.

_________________
Egal wie dumm man selbst ist, es gibt immer andere, die noch dümmer sind
Phantom1 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 390



BeitragVerfasst: Fr 12.12.03 17:56 
So hab meine Unit jetzt nochmals aktualisiert. Alle änderungen stehen im ersten Posting!
Luckie
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Fr 12.12.03 19:22 
UC-Chewie hat folgendes geschrieben:
Eventuell sind diese Verfälschungen aber auch gewünscht.
Wenn man herausfinden will, wie lange eine bestimmte Berechnung in einem Programm dauern wird und die Berechnung zum Messen auf Time-Critical durchführt, weiß man immer noch nicht, wie lange sie im fertigen Programm brauchen wird, da dort der laufende Thread nicht der einzige ist, der zum Zuge kommt.

Ja aber so kann man sagen, ohne andere störende Prozesse dauert sie exact genau so und so lange. Und wenn ich die Messung auf einem Rechner mit gleicher Hardware durch führe, aber im Hintergrund läuft der Indexdienst, ein Virenscanner und der Typ hat SETI laufen, dann bekomme ich mehr oder weniger exact die gleichen Werten. Und das ist ja auch das Ziel. Denn dann hab eich Werte unabhängig von laufenden Hintergrundprozesses.