a Microseconds counter


A new article has been written that describes a counter component. Look [ HERE ].

Introduction

For time measurements, the Delphi programmer may use a milliseconds counter.
This counter variable is of type cardinal and delivers the expired time in milliseconds since Windows was activated.
(So, it overflows after 49 days).
However, the accuracy is not very good, because the counter is not updated every millisecond but much less.
The following little program shows the behaviour of the timer:
    procedure TForm1.Button1Click(Sender: TObject);
    var t : cardinal;
    begin
     t := gettickcount;
     while gettickcount = t do;
     label1.Caption := inttostr(gettickcount-t);
    end;
    
In this case, label1 shows mostly a count of 16 and sometimes a count of 15 which indicates that the timer value
is incremented only once per 15,xxx milliseconds.
Also, most processes complete in less than a millisecond so, for accurate measurement,
they have to be repeated hundreds of times.
The Pentium processor however has a 64 bit counter that is updated every clock cycle.

The RDTSC instruction (code $0F,$31) copies bits 0..31 to register EAX and bits 32..63 to register EDX.
This allows for accurate measurements of short execution times.
Problem however is, that the CPU clock speed is system dependable.
To calculate elapsed time in microseconds, first the clock frequency of the processor must be calculated.
This is accomplished by sampling the CPU's 64 bit clock register, wait 500 milliseconds using the milliseconds timer,
then sampling the CPU clock counter again.
The difference of the CPU clock values divided by the elapsed time in milliseconds * 10-3
makes the clock speed in MHz (megahertz).
Once the clock speed is set, proces time is calculated by
    - sampling the CPU clock
    - execute the process
    - sample the CPU clock again
    - calculate the difference of the clocks
    - convert this difference to the "double" floating point format
    - divide this difference by the previously set CPU clock frequency
To implement such time measurements, two cases must be considered
    1. the Delphi version supports 64 bit integers
    2. the Delphi version does not support 64 bit integers

Support of 64 bit integers

See listing below:
    unit Unit1;
    {
       a microseconds clock for Delphi versions
       that support 64 bit integers (type = Int64)
    }
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        StaticText1: TStaticText;
        Button2: TButton;
        StaticText2: TStaticText;
        Label1: TLabel;
        Button3: TButton;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure Button3Click(Sender: TObject);
        procedure FormActivate(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
      CPUclock : double;    //CPU clock speed in MHz
    
    implementation
    
    {$R *.dfm}
    
    procedure getCPUticks(var count: Int64);
    //store 64 bits CPU clock in variable count
    begin
     asm
      mov ECX,count;
      RDTSC;          //lower 32 bits --> EAX, upper 32 bits ---> EDX
                      //RDTSC = DB $0F,$31
      mov [ECX],EAX;
      add ECX,4;
      mov [ECX],EDX;
     end;
    end;
    
    procedure setCPUclock;
    //set variable CPUclock
    //call this procedure before measuring any process time
    var t1,t2 : cardinal;         //system clock ticks
        cput1,cput2 : Int64;      //CPU clock ticks
    begin
     t1 := getTickCount;          //get milliseconds clock
     while getTickCount = t1 do;  //sync with start of 1 millisec interval
     getCPUticks(cput1);          //get CPU clock count
     t1 := gettickcount;
     repeat
      getCPUticks(cput2);         //get CPU clock count
      t2 := gettickcount;
     until t2-t1 >= 500;
     CPUclock := (cput2-cput1)/((t2-t1)*1e3);      //set CPU clock in microsecs
     form1.statictext2.caption := formatfloat('####',CPUclock)+' MHz';
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    //button1 enables display of CPU ticks in statictext1
    var count: Int64;
    begin
     getCPUticks(count);
     statictext1.Caption := inttostr(count);
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    //a process of which time is measured
    var t1,t2 : Int64;
        i : integer;
        x,y : double;
    begin
     getCPUticks(t1);
     x := 1000;             //process starts here
     y := 1;
     for i := 1 to 1000 do
      begin
       x := (x + y)2;
       y := 1000x;
      end;                 //process ends here
     getCPUticks(t2);
     statictext1.Caption := formatfloat('####.####',(t2-t1)/CPUclock) + ' microsecs';
    end;
    
    procedure TForm1.Button3Click(Sender: TObject);
    begin
     application.ProcessMessages;
     setCPUclock;
    end;
    
    procedure TForm1.FormActivate(Sender: TObject);
    begin
     application.ProcessMessages;  
     setCPUclock;
    end;
    
    end.
    

64 bit integers not supported

In this case, a variable type I64 is created.
type I64 = array[0..1] of cardinal

function I64ToFloat(a : I64) : double
converts the new I64 format to floating point double

procedure Diff64(t2,t1 : I64)
calculates the difference t2 := t2 - t1

Below is the source code
    unit Unit1;
    {
       a microseconds clock for Delphi versions
       that do not support 64 bit integers
       a type I64 = array[0..1] of cardinal is used instead
    }
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        StaticText1: TStaticText;
        Button2: TButton;
        StaticText2: TStaticText;
        Label1: TLabel;
        Button3: TButton;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure FormActivate(Sender: TObject);
        procedure Button3Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
     I64 = array[0..1] of cardinal;  //64 bit integer
    
    var
      Form1: TForm1;
      CPUclock : double;    //CPU clock speed in MHz
    
    implementation
    
    {$R *.dfm}
    
    procedure getCPUticks(var count: I64);
    //store 64 bits CPU clock in variable count
    begin
     asm
      mov ECX,count;
      RDTSC;          //lower 32 bits --> EAX, upper 32 bits ---> EDX
                      //RDTSC = DB $0F,$31
      mov [ECX],EAX;
      add ECX,4;
      mov [ECX],EDX;
     end;
    end;
    
    procedure diff64(var a,b : I64);
    //set a to a-b
    begin
     asm
      mov ECX,a;
      mov EAX,[ECX];
      mov EDX,[ECX+4];
      mov ECX,b
      sub EAX,[ECX];       //subtract
      sbb EDX,[ECX+4];     //subtract with borrow
      mov ECX,a;
      mov [ECX],EAX;
      mov [ECX+4],EDX;
     end;
    end;
    
    function I64ToFloat(const a : I64) : double;
    //convert I64 integer to floating point double
    begin
     result := a[0] + a[1]*4294967296;
    end;
    
    function I64ToHex(a : I64) : string;
    //convert 64 bits integer to hexadecimal string
    const v : array[0..15] of char =
              ('0','1','2','3','4','5','6','7',
               '8','9','a','b','c','d','e','f');
    var i,j : byte;        
    begin
     result := '';
     for i := 1 downto 0 do
      for j := 7 downto 0 do
       result := result + v[(a[i] shr (j*4)) and $f];
    end;
    
    procedure setCPUclock;
    //set variable CPUclock
    //call this procedure before measuring any process time
    var t1,t2 : cardinal;         //system clock ticks
        cput1,cput2 : I64;        //CPU clock ticks
    begin
     t1 := getTickCount;          //get milliseconds clock
     while getTickCount = t1 do;  //sync with update of 1 millisec interval
     getCPUticks(cput1);          //get CPU clock count
     t1 := gettickcount;          //get millisecs clock time
     repeat
      getCPUticks(cput2);         //get CPU clock count
      t2 := gettickcount;
     until t2-t1 >= 500;
     diff64(cput2,cput1);
     CPUclock := I64ToFloat(cput2)/((t2-t1)*1e3);  //set CPU clock in microsecs
     form1.statictext2.caption := formatfloat('####',CPUclock)+' MHz';
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    //button1 enables display of CPU ticks in statictext1
    var count: I64;
    begin
     getCPUticks(count);
     statictext1.Caption := I64ToHex(count);
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    //a process of which time is measured
    var t1,t2 : I64;
        i : integer;
        x,y,z : double;
    begin
     getCPUticks(t1);
     x := 1000;             //process starts here
     y := 1;
     for i := 1 to 1000 do
      begin
       x := (x + y)2;
       y := 1000x;
      end;                 //process ends here
     getCPUticks(t2);
     diff64(t2,t1);        //t2 := t2 - t1
     z := I64ToFloat(t2);
     statictext1.Caption := formatfloat('####.####',z/CPUclock) + ' microsecs';
    end;
    
    procedure TForm1.FormActivate(Sender: TObject);
    //calculate CPU clock speed
    begin
     application.processmessages;
     setCPUclock;
    end;
    
    procedure TForm1.Button3Click(Sender: TObject);
    //recalculate the CPU clock
    begin
     application.processmessages;
     setCPUclock;
    end;
    
    end.
    

The Projects (Delphi-7)

Click [here] to download the Delphi project for versions that support 64 bit integers (Int64)
Click [here] to download the Delphi project for versions that do not support 64 bit integers

The CPU clock speed is calculated at formactivate time.
Do not use formcreate as this (for some reason) yields a lower clock frequency.

The projects allow for display of the CPU clock ticks and also have a button which measures a simple process.
Also a button is added to recalculate the CPU clock frequency.
Note, that in most cases the measured time (and calculated CPU clock speed) is accurate within 0,1%.
But in rare cases, clock speed is much lower because the measuring process apparently was interrupted
by Windows.