Een microseconden teller

Er is een nieuw artikel waarin een timer component wordt beschreven, kijk [ HIER ]

Klik [hier] voor het Delphi project als 64 bits integers worden ondersteund (Int64)
Klik [hier] voor het Delphi project als geen 64 bits integers worden ondersteund

Inleiding

In Delphi programma's kan voor tijdmeting gebruik worden gemaakt van een milliseconden teller.
Deze 32 variable van het type cardinal levert de tijd in milliseconden vanaf het opstarten van windows.
(Er treedt dus een overflow op na 49 dagen)
De nauwkeurigheid is niet erg goed, omdat de teller niet elke milliseconde wordt verhoogd,
maar slechts 60 maal per seconde.
Het volgende programmaatje toont dit gedrag van de milliseconde teller:
    procedure TForm1.Button1Click(Sender: TObject);
    var t : cardinal;
    begin
     t := gettickcount;
     while gettickcount = t do;
     label1.Caption := inttostr(gettickcount-t);
    end;
    
Zoals blijkt, toont label1 meestal een waarde van 16 en soms van 15.

De meeste processen duren minder dan milliseconden, zodat voor een nauwkeurige tijdmeting
een proces honderden malen herhaald moet worden.
De Pentium processor heeft echter ook een 64 bit teller, die elke clock cycle wordt verhoogd.

De RDTSC instructie (code $0F,$31) kopieert bits 0..31 naar register EAX en bits 32..63 naar register EDX.
Hiermee is nauwkeurige tijdmeting mogelijk van kort durende processen.
Maar er is nog een probleempje: de CPU kloksnelheid kan per systeem verschillen.

Om tijd te kunnen meten, moet eerst de klokfrequentie worden berekend.
Hiervoor maken we éénmalig gebruik van de milliseconde teller.
Dat gaat als volgt:
Lees het 64 bit CPU klokregister, wacht 500 milliseconden en lees het 64 bit register opnieuw.
Door het klokverschil * 0,001 te delen door 500 ontstaat de kloksnelheid in megahertz.
Is eenmaal de kloksnelheid bekend, dat wordt tijdsduur gemeten door:
    - de CPU klok te lezen
    - het te meten proces uit te voeren
    - de CPU klok opnieuw te lezen
    - het verschil van de klokwaardes te berekenen
    - dit verschil om te zetten naar "double" floating point formaat
    - dit verschil te delen door de berekende klokfrequentie
Dan moeten er nog twee gevallen worden ondercheiden
    1. de Delphi versie ondersteunt 64 bit integers
    2. de Delphi versie ondersteunt geen 64 bit integers

Support van 64 bit integers

zie de listing hieronder:
    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 niet ondersteund

In dit geval wordt een 64 bit integertype genaamd I64 gedefinieerd.
type I64 = array[0..1] of cardinal

function I64ToFloat(a : I64) : double
converteert het nieuwe I64 formaat naar floating point double.

procedure Diff64(t2,t1 : I64)
berekent het verschil t2 := t2 - t1

Hier staat de 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.
    

De projecten (Delphi-7)

De CPU kloksnelheid wordt berekend na het event formactivate.
Gebruik hiervoor niet formcreate want dit levert een te lage waarde op om een of andere reden.

De projecten berekenen de tijdsduur van een simpel proces en tonen ook de CPU klokwaarden.
Merk op, dat de kloksnelheid meestal binnen 0,1% nauwkeurig wordt berekend.
Maar in zeldzame gevallen is de berekende kloksnelheid lager omdat blijkbaar tijdens het meetproces
een taak van windows werd afgehandeld.