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:
- it is desirable to estimate the efficiency of several code fragments simultaneously;
- if the code to be tested occurs several times within the program, it is desirable to calculate the minimum, maximum and average execution time.
Two classes met to the above requirements have been developed:
- TrsProfilerPoint - contains data on one estimation point (fragment to be tested).
- TrsProfiler - contains the list of points TrsProfilerPoint.
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.