Millisecond timing
Many applications require accurate timing, such as the time that
a data packet takes to transverse a network, or the time that
a user takes between keystrokes. Win32 has a whole host of timing
functions which can be used to accurately measure time.
- GetCurrentTime. Returns clock ticks since Windows started.
- GetTickCount. Returns clock ticks since Windows started.
- KillTimer. Destroys a timer.
- QueryPerformanceCounter.
- QueryPerformanceFrequency.
- SetTimer. Creates a timer.
The GetTickCount can be used in applications which only require
a preciseness of 1 ms, whereas QueryPerformanceCounter can be
used in higher resolution applications (if supported by the hardware).
Figure 1 shows an outline design in Delphi, where a button is
used to start a timer, and one is used to end a timer. Program
1 show an outline program.
|
Basic timer
|
| unit
Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Label1: TLabel;
Button2: TButton;
Edit2: TEdit;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
start: word;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender:
TObject);
begin
start:=GetTickCount;
edit1.text:='Started...';
end;
procedure TForm1.Button2Click(Sender:
TObject);
var endval:word;
begin
endval:=GetTickCount;
edit1.text:=inttostr(endval-start) + ' ms';
end;
procedure TForm1.FormCreate(Sender:
TObject);
var secs:integer;
begin
secs:=gettickcount div 1000;
edit2.text:=inttostr(secs div 60) + ' mins';
end;
end.
|
Thus we can measure events to a precision of milliseconds with
the GetTickCount function. This is achieved by setting a start
variable at the start of the event, and then when the event occurs
the endval is taken. The difference between the two gives the
time difference.
High-precision timing (microseconds)
The QueryPerformance Counter can determine the number of milliseconds
since the system started, and uses a 64-bit wide unsigned value
to get the best possible resolution. Along with this the QueryPerformanceFrequency
can be used to determine the resolution of the high-resolution
counter. Intially the counter is tested to see if it can support
the high-resolution, with:
|
Testing timer, and getting
a count value
|
| If
QueryPerformanceCounter(Counter1) Then
|
This will also get a 64-bit integer value (int64), into Counter1.
The resolution of the timer can be determined from:
|
Testing resolution
|
| QueryPerformanceFrequency(Freq);
|
Which returns the number of increments made by the timer, every
second. Thus it gives the frequency of the timer. Again this returns
a 64-bit integer value. Program 2 gives example code which will
determine the resolution of the timer, and will also time the
amount of time taken by the API call. In Figure 2 it can be seen
that, in this case, that the resolution of the timer is 0.27937
uS (microseconds), and that the timer is operating at 3,579,545
Hz. It can also be seen that the time to complete the API call
is 2.51429 uS (microseconds).
Thus we can get timings of less than 1 millionth of a second
using this timer. Again to time an event, we would set an initial
counter value (such as counter1), and then when the event had
occurred we take the new time count, and compare them.
|
Precise timer
|
| unit
timer2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender:
TObject);
var Counter1, Counter2, freq:int64;
res, t: real;
str:string;
begin
If QueryPerformanceCounter(Counter1) Then
begin
QueryPerformanceCounter(Counter2);
QueryPerformanceFrequency(Freq);
memo1.Lines.add('Minimum resolution: 1/' + inttostr(Freq)
+ ' sec');
str:=format('%8.5f', [1/Freq/1e-6]);
memo1.Lines.add('Minimum resolution: ' + str + ' us');
t:=(counter2-counter1)/freq;
str:=format('%8.5f', [t/1e-6]);
memo1.Lines.add('API Overhead: ' + str + ' us');
end
Else
memo1.Lines.add('High-resolution counter not supported.');
end;
end.
|
Timing network events
A good example of using the high precision timers is to time
network events. For example we could time the amount of time that
a client takes to make a connection with a server. This is an
important factor, and obviously depends on the amount of traffic
on the network, and also on the loading of the server.
Program 2 shows a sample program which measures the amount of
time it takes for a client to make a connection with a server.
The highlighed text is the code which has been added. Initially
the start value is set when the connection is requested, and the
end time is set when the SocketEvent is generated. A sample run
is shown in Figure 3. It can be seen from this that the connection
time is 499.8 ms.
|
Precise timer
|
| unit
client3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms,Dialogs, ScktComp, StdCtrls;
type
TForm1 = class(TForm)
ClientSocket: TClientSocket;
Label1: TLabel;
Label2: TLabel;
GroupBox1: TGroupBox;
Edit3: TEdit;
Label3: TLabel;
Label5: TLabel;
Button1: TButton;
Memo2: TMemo;
GroupBox2: TGroupBox;
CheckBox1: TCheckBox;
Label4: TLabel;
Label6: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Label7: TLabel;
Edit5: TEdit;
Button2: TButton;
ComboBox1: TComboBox;
Edit4: TEdit;
Label8: TLabel;
procedure ClientSocketRead(Sender:
TObject; Socket: TCustomWinSocket);
procedure Button1Click(Sender: TObject);
procedure ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
start: int64;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender:
TObject);
var port:integer;
begin
ClientSocket.Host:=edit3.text;
ClientSocket.ClientType:=ctNonBlocking;
ClientSocket.Active:=true;
QueryPerformanceCounter(start);
// get start
end;
procedure TForm1.ClientSocketConnect(Sender:
TObject;
Socket: TCustomWinSocket);
var endcount, freq: int64;
t:real;
str: string;
begin
Checkbox1.Checked:=true;
Edit1.Text:=Socket.LocalAddress;
Edit2.Text:=Socket.LocalHost;
Memo2.Lines.Clear;
edit5.Text:='';
QueryPerformanceCounter(endcount);
QueryPerformanceFrequency(Freq);
t:=(endcount-start)/freq;
str:=format('%8.5f ms', [t/1e-3]);
edit4.text:=str;
end;
procedure TForm1.ClientSocketRead(Sender:
TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
procedure TForm1.Button2Click(Sender:
TObject);
begin
ClientSocket.Socket.SendText(edit5.text +
chr(10)+chr(13)+chr(10)+ chr(13));
end;
procedure TForm1.FormCreate(Sender:
TObject);
begin
ComboBox1.items.add('Echo'); // Port 7
ComboBox1.items.add('Daytime'); // Port 13
ComboBox1.items.add('Char gen'); // Port 19
ComboBox1.items.add('FTP'); // Port 21
ComboBox1.items.add('Telnet'); // Port 23
ComboBox1.items.add('STMP'); // Port 25
ComboBox1.items.add('WWW'); // Port 80
ComboBox1.items.add('Test (1001)'); // Port 1001
end;
procedure TForm1.ComboBox1Change(Sender:
TObject);
var val:integer;
begin
val:=ComboBox1.ItemIndex;
if (val=0) then ClientSocket.Port:=7
else if (val=1) then ClientSocket.Port:=13
else if (val=2) then ClientSocket.Port:=19
else if (val=3) then ClientSocket.Port:=21
else if (val=4) then ClientSocket.Port:=23
else if (val=5) then ClientSocket.Port:=25
else if (val=6) then ClientSocket.Port:=80
else ClientSocket.Port:=1001
end;
end.
|
Ref: Extract from Chapter 15, Mastering Delphi, W.Buchanan,
Palgrave, 2002.