|
|
a Delphi counter component |
|
|
Introduction
This article describes a microseconds counter in the Delphi (version 7) programming language.
The accuracy of this counter is in nanoseconds.
We use the internal processor clock, a 64 bit counter which counts clock cycles.
The program is organized as a Class to be installed in the Delphi component pallette.
Purpose of this project is to measure execution times.
Theory
A CPU (chip) has a certain clock rate, say 3GHz (Giga Hertz).
This means that per second 3 billion "ticks" take place.
Before converting CPU clock ticks to time, we have to find out the CPU clock frequency.
There also is a system clock, which counts milliseconds.
Function getTickCount returns this time, the number of milliseconds since power on.
However, this clock does not behave as expected.
See the program below, where subsequent increment values are written in a TMemo component:
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.
This is the result:
Milliseconds are counted, but the counter is updated only per 15 or 16 milliseconds.
Calculation of the CPU clock frequency
Using the GetTickCount we count a period of 500 milliseconds.
In this time we count the number of ticks of the 64 bit CPU clock.
To read the 64 bit CPU clock:
function TTimer64.getCPUtime : Int64;
asm
RDTSC; //dw $310F
end;
Note: TTimer64 is the Class name. See later for details.
Note: for some reason, the following code does not work:
function TTimer64.getCPUtime : Int64;
begin
asm
RDTSC; //dw $310F
end;
end;
The RDTSC (or hexadecimaal $310F) command writes the lowest 32 bits in register EAX
and the highest 32 bits in register EDX.
These registers also hold function parameters of type Int64.
Calculation of the CPU clock frequency:
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;
First we wait until the milliseconds clock increments.
CPUtime is the property that stands for function getCPUTime.
Fstart is set to the CPU clock.
Now we wait 500 milliseconds.
Then Fend is set to the CPU clock.
FCPUclock is the (floating point double) value we need.
Measuring elapsed time
Now we know the CPU clock frequency we are able to measure time accurately.
Before starting the proces we set the CPU clock in a startvalue.
After the process we place the CPU clock in an endValue. (both type Int64).
Time = (endvalue - startvalue) / clockfrequency.
Why? 1/clockfrequency = time between 2 clock cycles.
endvalue - startvalue = number of CPU ticks.
The 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.
Procedures start and stop place the CPU clock in Fstart and Fstop.
Property ElapsedTime calculates the time.
This value may also be returned as string by ElapsedTimeString.
The ancestor class is TComponent.
After installation in the component pallette a bitmap image is placed on the form,
but this image is not painted at runtime.
Application
Place a timer on the main form with name CPUt.
To display the CPU clock frequency in label1:
label1.caption := CPUt.CPUclockstring;
To pauze a program we use the comand: application.processmessages.
This returns control to Windows to process pending messages,
resulting from clicking a button or pressing a key.
We wonder how much time Windows needs.
On our main form we place a timer, TButton and TLabel component.
To the ButtonClick event we add the code:
with CPUt do
begin
start;
application.processmessages;
stop;
label1.caption := CPUt.ElapsedtimeString;
end;
Just over 85 microseconds.
|
|