[Back to TIMING SWAG index] [Back to Main SWAG index] [Original]

    (*
      Written by Yuval Melamed <melamed@star.net.il>, 25-Oct-1997.

      THIS CODE USES MSRs - REQUIRES PENTIUM AND ABOVE.
      This source code is mainly to show an advanced way to measure the
      performance of operations.
      The ClockOn/ClockOff procedure can be put inside a unit, that you
      can use instead of the old, unreliable but popular timer-based
      testers.

      What makes my procedures so accurate, is the counting of internal
      clock cycles (aka proccessor's speed in MHz), instead of relying on the
      slow RTC (only 18.2 times a sec, compared to 133,000,000 clock cycles on
      a 133MHz Pentium.

      The usage of this is great - starting from small yet accurate speed
      testers for regular procedures, ending by the test of very very gentle
      operations, such as different memory accessing methods !

      To make this procedures to measure TIME instead of CLOCKS, you can use:

        ClockOn;
        Delay(1000); {use CRT, or use Int 15h/AH=86h}
        ClockOff;
        CPUSpeed := Clocks / 1000000; {var CPUSpeed : Real}

      and then produce a *nano-second* (!!!) accurate results by using:

        ClockOn;
        ... whatever ... {put here the operation to measure}
        ClockOff;
        NanoSeconds := Clocks / CPUSpeed; {var NanoSeconds : extended}
    *)

    const
      Times = 1000000; {times to run the test loop}

    var
      Index : Longint;   {loop counter}
      Clocks,            {clock cycles counter}
      Resolution : comp; {measurement-resolution in clock cycles}

    (* This procedure 'starts' the timer.
       It actually reads the value of the TSC (Time Stamp Counter) of the
       Pentium/Compatible proccessor (TSC is one of the MSRs = Model Specific
       Registers, first introduced with the Pentium).
       The TSC is set to 0 on cold-boot, and increments every internal clock
       cycle (nnn million times on a nnnMHz proccessor).
       The TSC is a 64-bit register, thus we use a comp type to hold it.
       We use a 10h-ECX-indexed 'rdmsr' (Read MSR) even though there's a
       specific opcode to read the TSC (rdtsc), because 'rdmsr' works better
       in most cases.                                                         *)
    procedure ClockOn; assembler;
    asm
      db 66h; mov cx,0010h; dw 0000h   (* mov ecx,10h                *)
      db 0Fh,32h                       (* rdmsr                      *)
      db 66h; mov word ptr Clocks,ax   (* mov dword ptr Clocks,eax   *)
      db 66h; mov word ptr Clocks+4,dx (* mov dword ptr Clocks+4,edx *)
    end;

    (* This procedure 'stops' the timer.
       It reads the TSC again, and substract the last TSC read from it (to
       substract 64-bit register, I use 32-bit sub's, with overflow check). *)
    procedure ClockOff; assembler;
    asm
      db 66h; mov cx,0010h; dw 0000h   (* mov ecx,10h                *)
      db 0Fh,32h                       (* rdmsr                      *)
      db 66h; sub ax,word ptr Clocks   (* sub eax,dword ptr Clocks   *)
      db 66h; sbb dx,word ptr Clocks+4 (* sbb edx,dword ptr Clocks+4 *)
      db 66h; mov word ptr Clocks,ax   (* mov dword ptr Clocks,eax   *)
      db 66h; mov word ptr Clocks+4,dx (* mov dword ptr Clocks+4,edx *)
    end;

    (* This procedure does nothing (using NOP mnemonic), except being a
       'standard' pascal procedure, that is - 'begin end;' bounded.     *)
    procedure PascalProc;
    begin
      asm
        nop
      end;
    end;

    (* This procedure uses the 'assembler' directive, to prevent 'call'
       command to it, from allocating superflouos stack memory (see Help
       for details, or track this code with a debugger).
       On my configuration (Turbo-Pascal 7.0, Pentium 133MHz) it is 225% (!)
       faster than a standard procedure.                                     *)
    procedure AssemblerProc; assembler;
    asm
      nop
    end;

    (* This is not actually a procedure, since it never gets to be really
       'call'ed. That is, because 'inline' means the code is inserted
       directly each time it is 'called' (again, see Help/Debugger).
       On my PC, it is 900% faster than using a regular Pascal procedure to
       implement the same code... GO OPTIMIZE !!! ;)                        *)
    procedure InlineProc; inline
    (
      $90 {90h is the value of the byte-sized NOP mnemonic}
    );

    begin
      Writeln;
      Writeln(Times, ' calls to different types of identical procedures :');
      ClockOn;
      for Index := 1 to Times do;
      ClockOff;
      Resolution := Clocks; {use 'Resolution' to filter the loop timings}

      ClockOn;
      for Index := 1 to Times do PascalProc;
      ClockOff;
      Writeln('  Standard Pascal procedure            : ',
        Clocks-Resolution:10:0, ' clks.');

      ClockOn;
      for Index := 1 to Times do AssemblerProc;
      ClockOff;
      Writeln('  assembler-directived procedure       : ',
        Clocks-Resolution:10:0, ' clks.');

      ClockOn;
      for Index := 1 to Times do InlineProc;
      ClockOff;
      Writeln('  procedure of type inline (no call)   : ',
        Clocks-Resolution:10:0, ' clks.');
    end.

[Back to TIMING SWAG index] [Back to Main SWAG index] [Original]