Autor |
Beitrag |
Phantom1
Beiträge: 390
|
Verfasst: 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:
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 @@1: POPFD TEST EAX,EAX JZ @@2 MOV EAX,1 DW 0A20Fh TEST EDX,010h SETNZ AL @@2: POP 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:
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; 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
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: 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
Beiträge: 390
|
Verfasst: Do 11.12.03 18:36
Danke für den Tipp, habe den Code oben soeben aktualisiert.
|
|
Currywurst
Beiträge: 50
Win XP Pro
D3 Pro, D5 Std, D6 Pers
|
Verfasst: Fr 12.12.03 06:53
du solltest ihn nocheinmal aktualisieren, denn so war das sicher nich ganz gemeint
|
|
Phantom1
Beiträge: 390
|
Verfasst: 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
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: 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
Beiträge: 50
Win XP Pro
D3 Pro, D5 Std, D6 Pers
|
Verfasst: 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
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: 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:
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
Beiträge: 390
|
Verfasst: 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 ich werd mich gleich mal an die arbeit machen
@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
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: 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
Beiträge: 390
|
Verfasst: 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
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: 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
Beiträge: 531
WinXP
D5 Ent
|
Verfasst: 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
Beiträge: 390
|
Verfasst: 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
|
Verfasst: 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.
|
|
|