|
Een microseconden teller component in Delphi |
|
|
Inleiding
Dit artikel beschrijft een microseconden teller in de programmeertaal Delphi (versie 7).
De nauwkerigheid is in nanoseconden.
Hiervoor wordt de interne processor klok gebruikt, een 64 bits teller die elke clockcycle ophoogt.
Van de programmacode is een Class gemaakt die als component geïnstalleerd kan worden.
De bedoeling van dit project is het nauwkeurig meten van de tijdsduur.
Werkwijze
Een CPU (chip) heeft een bepaalde clock frequentie, bijvoorbeeld 3GHz (Giga Hertz).
Dat betekent dat per seconde er 3 miljard "tikken" plaatsvinden.
Eerst moet worden uitgezocht wat de clockfrequentie is, anders kan geen tijdsduur worden berekend.
Er is ook een andere clock, die milliseconden telt.
De functie getTickCount levert die clock.
Maar daar is wat vreemds mee aan de hand.
We voeren het volgende programma uit, dat opvolgende verhogingen van de clock in een TMemo component zet:
procedure TForm1.Button1Click(Sender: TObject);
var n : byte;
t1,t2 : dword;
s : string;
begin
memo1.Clear;
n := 0;
t1 := GetTickCount;
repeat
t2 := getTickcount;
if t2 <> t1 then
begin
inc(n);
s := inttostr(t2 - t1);
t1 := t2;
memo1.lines.add(s);
end
else application.ProcessMessages;
until n = 25;
end;
end.
We zien dan dit:
Er worden milliseconden geteld, maar de teller wordt maar eens per 15 of 16 milliseconden bijgewerkt.
Berekenen van de CPU kloksnelheid
Met de GetTickCount waarde kunnen we nauwkeurig 500 milliseconden tellen.
In deze afgepaste tijd meten we ook het aantal "tikken" van de 64 bits CPU klok.
Die laatste waarde leggen we vast in een floating point double variabele.
Lezen van de CPU clock gaat zo:
function TTimer64.getCPUtime : Int64;
asm
RDTSC; //dw $310F
end;
Opmerking: TTimer64 is de naam van de Class. Later meer details.
Opmerking: de volgende code werkt om de een of andere reden niet:
function TTimer64.getCPUtime : Int64;
begin
asm
RDTSC; //dw $310F
end;
end;
De RDTSC (of hexadecimaal $310F) schrijft de laagste 32 bits in register EAX
en de hoogste 32 bits in register EDX.
Dat zijn ook de registers waar enkele functie parameters van het type Int64 in staan.
Nu het meten van de CPU kloksnelheid:
constructor TTimer64.create(Aowner : TComponent);
var t1,t2 : dword;
begin
inherited create(AOwner);
t2 := getTickCount;
repeat
t1 := getTickCount;
until t2 <> t1; //wait for millisecs counter to change
Fstart := CPUtime;
repeat
t2 := getTickCount;
until t2 - t1 >= 500;
Fend := CPUtime;
FCPUclock := (Fend - Fstart)/(t2 - t1) * 1e-3; //megahertz clock
end;
Eerst wordt gewacht tot de milliseconden teller is verhoogd.
CPUtime is een property die staat voor de functie getCPUTime.
Fstart krijgt de waarde van de CPU clock.
Nu wordt 500 milliseconden gewacht.
Dan krijgt Fend de waarde van de CPU clock.
FCPUclock is de (double) waarde die we zochten.
Meten van tijd
Nu de CPU kloksnelheid bekend is kunnen we nauwkeurig executietijden berekenen.
Voor het te meten proces zetten we een de CPU klok in een startwaarde.
Na het proces zetten we de CPU klok in een eindwaarde (alletwee type Int64).
Tijdsduur = (eindwaarde - beginwaarde) / klokfrequentie.
Immers: 1/klokfrequentie is de tijd tussen twee "CPU "tikken" en
eindwaarde - beginwaarde is het aantal tikken.
De timer Component
unit timercomponent;
interface
uses windows,classes,sysutils;
type TTimer64 = class(TComponent)
private
FCPUclock : double;
Fstart : Int64;
Fend : Int64;
protected
function getCPUclockString : string;
function getCPUtime : Int64;
function getCPUTimeString : string;
function getElapsedTime : double;
function getElapsedTimeString : string;
public
constructor create(Aowner:TComponent); override;
destructor destroy; override;
property CPUclock : double read FCPUclock;
property CPUclockString : string read getCPUclockString;
property CPUTimeString : string read getCPUTimeString;
procedure start;
procedure stop;
property ElapsedTime : double read getElapsedTime;
property ElapsedTimeString : string read getElapsedTimeString;
property CPUtime : Int64 read getCPUtime;
function CPUclockToTime(I64 : Int64) : double;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('system',[TTimer64]);
end;
function TTimer64.getCPUtime : Int64;
asm
RDTSC; //dw $310F
end;
constructor TTimer64.create(Aowner : TComponent);
var t1,t2 : dword;
begin
inherited create(AOwner);
t2 := getTickCount;
repeat
t1 := getTickCount;
until t2 <> t1; //wait for millisecs counter to change
Fstart := CPUtime;
repeat
t2 := getTickCount;
until t2 - t1 >= 500;
Fend := CPUtime;
FCPUclock := (Fend - Fstart)/(t2 - t1) * 1e-3; //microhertz clock
end;
destructor TTimer64.destroy;
begin
inherited destroy;
end;
procedure TTimer64.Start;
begin
FStart := CPUtime;
end;
procedure TTimer64.Stop;
begin
Fend := CPUtime;
end;
function TTimer64.getElapsedTime : double;
begin
result := (Fend - Fstart) / FCPUclock;
end;
function TTimer64.CPUclockToTime(i64 : Int64) : double;
begin
result := i64 / FCPUclock;
end;
function TTimer64.getElapsedTimeString : string;
const Fconvert = '#####0.0###';
var t : double;
begin
t := getElapsedTime;
result := formatfloat(Fconvert,t);
end;
function TTimer64.getCPUclockString : string;
const Fconvert = '###0.0###';
begin
result := formatfloat(FConvert,FCPUclock);
end;
function TTimer64.getCPUTimeString : string;
var i64 : int64;
begin
i64 := CPUTime;
result := inttostr(i64);
end;
end.
De procedures start en stop zetten de CPU klok in Fstart en Fstop.
Property ElapsedTime berekent de verlopen tijd.
De waarde kan ook meteen als string worden gegeven met ElapsedTimeString.
De class is afgeleid van TComponent.
Bij installatie als component wordt op het form wel een bitmap image (.dcr) gezet,
maar dit verdwijnt in RunTime.
Toepassing
Plaats een timer op het main form met naam CPUt.
Om de kloksnelheid in label1 te zetten:
label1.caption := CPUt.CPUclockstring;
Om een programma te pauzeren gebruiken we de opdracht: application.processmessages
Die geeft het commando even terug aan Windows om uitstaande events te verwerken,
bijvoorbeeld ontstaan omdat we op een knop klikten of een toets indrukten.
We vragen ons af hoeveel tijd Windows nodig heeft.
Plaats een timer op het main form met een TButton en een TLabel component.
Voeg deze code toe aan het ButtonClick event:
procedure TForm1.Button1Click(Sender: TObject);
begin
with CPUt do
begin
start;
application.ProcessMessages;
stop;
label1.Caption := elapsedtimestring;
end;
end;
Ruim 85 microseconden.
|