Introduction
Delphi has a function gettickcount to obtain the value of a milliseconds clock.
One millisecond is a very long time for most processes so, to obtain accurate time measurements,
processes have to be repeated thousends of times.
However, the Pentium processor has an internal 64 bit clock, that updates on each clock cycle.
So, by combining -once- the milliseconds clock and the clock cycle counter, we can calculate
the processors clock frequency.
And once the clock speed is known, execution times can be measured almost accurate to the nanosecond,
by using the clock speed as a correction factor.
Say, we count the number of clock cycles during 500milliseconds. Let's call this count c500.
Then clockspeed = c500 * 2*10-6 cycles / microsecond.
To measure the duration of a process, before and after this proces the 64 bit cycle count is loaded
in variables t1 and t2.
Then actual processing time in microseconds is (t2 - t1) / clockspeed.
Data format
Older versions of Delphi do not support the 64 bit integer format.
Therefore, a new data type is introduced to hold the 64 bits:
type TI64 = array[0..8] of byte;
Bits 0..7 of byte -0- hold bits 0..7 of the clock cycle count,
byte -1- holds bits 8..15 of the clock counter etc.
An extra byte -8- is added to facilitate integer divide by 10, to convert the
number to a decimal string.
Variables, Function and Procedure calls
var
CPUClock: extended; //floating point value holding cycles per microsecond
procedure GetCPUTicks(var i64 : TI64);//store CPU ticks in array[0..7]
function diff64(v1,v2 : TI64) : TI64; //calculate v2 - v1
function I64tofloat(I64 : TI64) : extended; //convert 64 bit integer to floating point format
function I64toStr(I64 : TI64) : string; //converts 64 bit integer to a string
function CyclesPerMicroSecond : extended;//calculate the clock speed
Function and Procedures
procedure GetCPUTicks(var i64 : TI64);
//store CPU ticks in array[0..7]
var i : byte;
label loop1,loop2;
begin
asm
mov ECX,i64 //save address
DB $0F,$31 // RDTSC command
// put CPU clock count in EAX (bits 0..31) and EDX (bits 32..63)
mov i,4
loop1:
mov [ECX],AL
shr EAX,8
inc ECX
dec i
jnz loop1
mov i,4
loop2:
mov [ECX],DL
shr EDX,8
inc ECX
dec i
jnz loop2
end;
end;
function I64tofloat(I64 : TI64) : extended;
var i : byte;
w : extended;
begin
result := 0;
w := 1;
for i := 0 to 7 do
begin
result := result + I64[i]*w;
w := w * 256;
end;
end;
function diff64(v1,v2 : TI64) : TI64;
//difference v2 - v1 of 2 64 bit integers
var borrow,i : Byte;
begin
borrow := 0;
for i := 0 to 7 do
begin
if v2[i] < v1[i]+borrow then
begin
result[i] := 256 + v2[i] - v1[i] - borrow;
borrow := 1;
end
else begin
result[i] := v2[i] - v1[i] - borrow;
borrow := 0;
end;
end;
end;
The following function is not really necessary, since values can be converted to
floating point and then to a character string.
It is only usefull to directly make a string from the clock count.
Figure 1. below shows the idea:
 |
| fig.1 |
The 64 bits are shifted 1 place to te left.
Then a (trial) subtract of 10 decimal (binary 1010) is done.
If succesfull, a "1" is inserted in bit 0 of byte -0-.
After 64 shifts and trial subtracts, byte -8- holds the remainder and
the lower bytes hold the quotient.
Then the remainder is stored as the first digit of the result string and byte -8- is cleared.
above process is repeated until the quotient equals zero.
function I64toStr(I64 : TI64) : string;
//convert a 64 bit integer to a string
const d = 10;
hb = $80;
msk = $7f;
var i,j,b,c : byte;
sum : word;
begin
result := '';
repeat
I64[8] := 0;
for i := 1 to 64 do
begin
for j := 8 downto 1 do
begin
c := I64[j-1] shr 7;
I64[j] := ((I64[j] and msk) shl 1) or c;
end;//for j
if I64[8] >= d then begin
b := 1;
I64[8] := I64[8] - d;
end
else b := 0;
I64[0] := ((I64[0] and msk) shl 1) or b;
end;//for i
insert(chr(ord('0') + I64[8]),result,0);
sum := 0;
for j := 0 to 7 do inc(sum,I64[j]);
until sum = 0;
end;
Process below calculates the clock frequency of the processor.
function CyclesPerMicroSecond : extended;
//calculate the clock speed
var t1,t2 : TI64;
t : DWORD;
begin
t := GetTickCount;//get the milliseconds clock count
while t=GetTickCount do;//wait for increment, start of new count
GetCPUTicks(t1); //get the proc. cycles counter
t := t + 500;//set t to end time
while GetTickCount < t do; // wait ....
GetCPUTicks(t2); // cycle counter after 500millisecs
t1 := Diff64(t1,t2);
result := I64ToFloat(t1)*2e-6;
end;
When 64 bit integers are supported (type Int64)
Some websites list function below, however, this code does not work in Delphi-7
function GetCPUTicks: Int64;
asm
RDTSC; // = DB $0F,$31
end;
Code below does work:
procedure getCPUticks(var i : int64);
begin
asm
mov ECX,i;
RDTSC; //cpu clock in EAX,EDX
mov [ECX],EAX;
mov [ECX+4],EDX;
end;
end;
The other functions above (except "cyclespermicrosecond") are obsolete in this case.
Application
Store clock frequency in CPUclock
Show frequency in statictext1
procedure TForm1.Button1Click(Sender: TObject);
begin
CPUclock := CyclesperMicroSecond;
statictext1.caption := 'CPU clock= '+formatfloat('####0.00',cpuclock) + 'Mhz');
end;
Measure the speed of a process.
The clock cycles are stored in statictext2.
Microseconds are stored in statictext3.
procedure TForm1.Button2Click(Sender: TObject);
//CPUclock must be set
var t1,t2 : TI64;
sum, i : LongInt;
et : extended;
s : string;
begin
sum := 0;
getCPUTicks(t1);
for i := 0 to 1000000 do inc(sum,i);//this is the process to measure
getCPUTicks(t2);
t1 := Diff64(t1,t2);
s := I64toStr(t1);
et := I64ToFloat(t1)/CPUclock;
statictext2.Caption := s;
statictext3.caption := formatfloat('####0.000',et);
end;