Calculate the execution time of code

Abstract: This article describes a method for calculating the Delphi code execution time. The source code of the calculation unit is given.


Let us try to calculate the execution time of the following function:

{Check: has a string, char with code<' '}
function isTextString(const Value: PChar): boolean;
var
  i: integer;
begin
  Result := False;
  for i:=0 to StrLen(Value)-1 do
    if Value[i]<' ' then begin
      Result := True;
      Break;
    end
end

How one can calculate the execution time of this code?

Intel Pentium CPUs have a powerful command ‘RDTSC’. Here is an extraction from Intel specs of this command: “Loads the current value of the processor’s time-stamp counter into the EDX:EAX registers. The time-stamp counter is contained in a 64-bit MSR. The high-order 32 bits of the MSR are loaded into the EDX register, and the low-order 32 bits are loaded into the EAX register. The processor increments the time-stamp counter MSR every clock cycle and resets it to 0 whenever the processor is reset.”
Note that RDTSC command returns the values in the Int64 format used in Delphi.

function GetCPUTick: Int64;
asm
  DB $0F,$31  // this is RDTSC command. Assembler, built in Delphi, 
              // does not support it,
              // that is why one needs to overcome this obstacle.
end;

Such a simple function allows the user to get the current value of the CPU time-stamp counter. The difference between the time-stamp counter values returned before and after execution of a code, allows for calculationg the execution time. In our case:

var
  ticks: int64;
  s: string;
.....
  ticks := GetCPUTick; //get the processor's time-stamp counter value
  isTextString(s);
  ticks := GetCPUTick - ticks; // number of processor clock cycles
  Label1.Caption := Format('Execute time %u ticks', [ticks]);
.....

However, it is not convenient to measure the execution time in clock cycles; thus, it ought to be converted, for example, to microseconds. To make such a conversion, it is necessary to know how many CPU clock cycles correspond to a microsecond of its work.

var
  CPUClock: extended;
....
function CalibrateCPU: int64;
var
  t: cardinal;
begin
  t := GetTickCount;
  while t=GetTickCount do;
  Result := GetCPUTick;           //get the time-stamp counter value 
  while GetTickCount<(t+400) do;  // delay for 0,4 sec
  Result := GetCPUTick - result;  // clock cycle number in 0,4 second
  CPUClock := 2.5e-6*Result;      // clock cycle number in 1 microsecond
end;

Now it will be easy to calculate the code execution time in microseconds:

function TicksToStr(const Value: int64): string;
begin
  Result := FloatToStrF(Value/CPUClock,fffixed,10,2)+ ' ms';
end;

Besides, it is possible to determine the CPU clock frequency:

procedure TForm1.Button1Click(Sender: TObject);
begin
  CalibrateCPU;
  Label1.Caption := Format('CPU clock = %f MHz', [CPUClock]);
end;

Now, we have developed the unit for calculation of code time execution:

var
  ticks: int64;
  s: string;
  CPUClock: extended;
.....
  CalibrateCPU;
  ticks := GetCPUTick; //get the time-stamp value
  isTextString(s);
  ticks := GetCPUTick - ticks; //number of clock cycles
  Label1.Caption := Format('Execute time %s', [TicksToStr(ticks)]);
.....

Let us examine the source code above. A program which will include this source code, must contain at least one additional declaration of several variables (ticks, CPUClock), call of CalibrateCPU, call of the code being tested, preceded and followed by calls of GetCPUTick. However, this way is not suitable for regular code efficiency estimation. To facilitate the procedure, we need to develop a dedicated class designed for the code efficiency estimation. This class will provide the user with additional service possibilities which have to meet the following requirements:

Two classes met to the above requirements have been developed:

The source code of the unit calculating a procedure execution time via these classes is shown below:

uses ....., UrsProfiler;

.....
  rsProfiler.Clear;    // remove old points
  rsProfiler[0].Start; // use of 0-point, start calculation
  isTextString(s);
  rsProfiler[0].Stop;  // use of 0-point. stop calculation
  Label1.Caption := rsProfiler[0].asString;
.....

Let us consider possible applications of the classes developed. One can try to accelerate the execution of a procedure whose efficiency is under estimation. Let us develop a project with several versions of such a procedure and calculate their execution time. This demo project can be downloaded here. The module UrsProfiler, which can be used for calculating the execution time of your own procedures, is also included with the demo project. Of course, this module will not replace the dedicated ‘code optimizers’ like VTune by Intel, but UrsProfiler is more suitable for calculating of a procedure execution time during its development and optimization.

Download

UrsProfiler.zip - Module that calculates the code execution time.
CalcTicks.zip - Demo project. Demonstrate how to use UrsProfiler module. The UrsProfiler module is included.

Possible ways of using the UrsProfiler module for optimization of various simple code fragments will be considered in future articles.