Why QueryperformanceCounter timed different from wall clock? - delphi

Hi I am using QueryperformanceCounter to time a block of code in Delphi. For some reason, the
Millisecond number I got by using QueryPerformanceCounter is quite different from my wall clock time by using a stopwatch. For example The stopwatch give me about 33 seconds, which seems right if not accuracy, but using QueryPerofomanceCounter will give me a number like 500 Milliseconds.
When step though my code, I can see that QueryPerformanceFrequencygives me correct CPU frequency for my CPU, 2.4G for Core2 E6600. So if the tick number is correct, (tick number / Freq) * 1000 should give me correct execution time for the code I am timing, but why not?
I know that for the code I am trying to timing, QeuryPerformanceCounter is probably over-killing as it took seconds rather than MillionSeconds, but I am more interested in understanding the reason for the time difference between wall clock and QueryPerormanceCounter.
My Hardware is E6600 Core2 and OS is Windows 7 X64 if it is relevant.
unit PerformanceTimer;
interface
uses Windows, SysUtils, DateUtils;
type TPerformanceTimer = class
private
fFrequency : TLargeInteger;
fIsRunning: boolean;
fIsHighResolution: boolean;
fStartCount, FstopCount : TLargeInteger;
procedure SetTickStamp(var lInt : TLargeInteger) ;
function GetElapsedTicks: TLargeInteger;
function GetElapsedMiliseconds: TLargeInteger;
public
constructor Create(const startOnCreate : boolean = false) ;
procedure Start;
procedure Stop;
property IsHighResolution : boolean read fIsHighResolution;
property ElapsedTicks : TLargeInteger read GetElapsedTicks;
property ElapsedMiliseconds : TLargeInteger read GetElapsedMiliseconds;
property IsRunning : boolean read fIsRunning;
end;
implementation
constructor TPerformanceTimer.Create(const startOnCreate : boolean = false) ;
begin
inherited Create;
fIsRunning := false;
fIsHighResolution := QueryPerformanceFrequency(fFrequency) ;
if NOT fIsHighResolution then
fFrequency := MSecsPerSec;
if startOnCreate then
Start;
end;
function TPerformanceTimer.GetElapsedTicks: TLargeInteger;
begin
result := fStopCount - fStartCount;
end;
procedure TPerformanceTimer.SetTickStamp(var lInt : TLargeInteger) ;
begin
if fIsHighResolution then
QueryPerformanceCounter(lInt)
else
lInt := MilliSecondOf(Now) ;
end;
function TPerformanceTimer.GetElapsedMiliseconds: TLargeInteger;
begin
result := (MSecsPerSec * (fStopCount - fStartCount)) div fFrequency;
end;
procedure TPerformanceTimer.Start;
begin
SetTickStamp(fStartCount) ;
fIsRunning := true;
end;
procedure TPerformanceTimer.Stop;
begin
SetTickStamp(fStopCount) ;
fIsRunning := false;
end;
end.

This code just works for me, maybe you can try it:
var
ifrequency, icount1, icount2: Int64;
fmsec: Double;
begin
QueryPerformanceFrequency(ifrequency);
QueryPerformanceCounter(icount1);
Sleep(500);
QueryPerformanceCounter(icount2);
fmsec := 1000 * ((icount2 - icount1) / ifrequency);
end;
fmsec is about 499.6 or something like that.
Note: Don't rely on Now or TickCount for small numbers: they have an interval of about 10ms (depending on Windows version)! So duration of "sleep(10)" can give 0ms if you use Now and DateUtils.MillisecondsBetween
Note 2: Don't rely on QueryPerformanceCounter for long durations, because it's time can slowly go away during a day (about 1ms diff per minute)

If your hardware supports dynamic frequency scaling, it implies that QueryPerformanceFrequency cannot return a static value continuously describing a dynamically changing one. Whenever something computationally aggressive starts, the adapting CPU speed will prevent exact measurements.
At least, it was experienced with my notebook - as it changed to the higher clock rate, QueryPerformanceCounter based measurements were messed up.
So, regardless of the higher accuracy offered, I still use GetTickCount most of the time for such purposes (but DateTime based measurements are also OK, as mentioned before, except if time zone switches may occur), with some "warm-up" code piece that starts eating up the CPU power so the CPU speed is at its (constant) maximum as the relevant code piece starts executing.

You should post a code snippet demonstrating the problem...but I would assume an error on your part:
Milliseconds := 1000 * ((StopCount - StartCount) / Frequency);
If you are comparing to a stop watch, you can likely take the easier route and just capture the TDateTime before and after (by using Now()) and then use the DateUtils MilliSecondSpan() method to calculate difference:
var
MyStartDate:TDateTime;
MyStopDate:TDateTime;
MyTiming:Double;
begin
MyStartDate := Now();
DoSomethingYouWantTimed();
MyStopDate := Now();
MyTiming := MilliSecondSpan(MyStopDate, MyStartDate);
DoSomethingWithTiming(MyTiming);
end;

I use an NTP server to sync the PC clock periodically, the PC clock over a large amount of time to adjust the QueryPerformanceCounter "tick" time, and the calibrated QueryPerformanceCounter time for precise time measurements. On a good server where the clock drift is low it means that I have accuracy over time periods to much less than a millisecond and the clock times of all my machines synchronised to within a millsecond or two. Some of the relevant code is attached below:
function NowInternal: TDateTime;
const
// maximum time in seconds between synchronising the high-resolution clock
MAX_SYNC_TIME = 10;
var
lPerformanceCount: Int64;
lResult: TDateTime;
lDateTimeSynchronised: Boolean;
begin
// check that the the high-resolution performance counter frequency has been
// initialised
fDateTimeCritSect.Enter;
try
if (fPerformanceFrequency < 0) and
not QueryPerformanceFrequency(fPerformanceFrequency) then
fPerformanceFrequency := 0;
if fPerformanceFrequency > 0 then begin
// get the return value from the the high-resolution performance counter
if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
QueryPerformanceCounter(lPerformanceCount) then
lResult := fWindowsStartTime +
lPerformanceCount / fPerformanceFrequency / SecsPerDay
else
lResult := CSI_NULL_DATE_TIME;
if (MilliSecondsBetween(lResult, Now) >= MAX_CLOCK_DIFF) or
(SecondsBetween(Now, fLastSyncTime) >= MAX_SYNC_TIME) then begin
// resynchronise the high-resolution clock due to clock differences or
// at least every 10 seconds
lDateTimeSynchronised := SyncDateTime;
// get the return value from the the high-resolution performance counter
if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
QueryPerformanceCounter(lPerformanceCount) then
lResult := fWindowsStartTime +
lPerformanceCount / fPerformanceFrequency / SecsPerDay;
end else
lDateTimeSynchronised := False;
if MilliSecondsBetween(lResult, Now) >= (MAX_CLOCK_DIFF * 2) then
// default the return value to the standard low-resolution value if
// anything has gone wrong
Result := Now
else
Result := lResult;
end else begin
lDateTimeSynchronised := False;
// default the return value to the standard low-resolution value because
// we cannot use the high-resolution clock
Result := Now;
end;
finally
fDateTimeCritSect.Leave;
end;
if lDateTimeSynchronised then
CsiGlobals.AddLogMsg('High-resolution clock synchronised', CSI_LC_CLOCK);
end;
function SyncDateTime: Boolean;
var
lPriorityClass: Cardinal;
lThreadPriority: Integer;
lInitTime: TDateTime;
lNextTime: TDateTime;
lPerformanceCount: Int64;
lHighResCurrentTime: TDateTime;
lLowResCurrentTime: TDateTime;
begin
// synchronise the high-resolution date/time structure (boost the thread
// priority as high as possible during synchronisation)
lPriorityClass := CsiGetProcessPriorityClass;
lThreadPriority := CsiGetCurrentThreadPriority;
try
CsiSetProcessPriorityClass(REALTIME_PRIORITY_CLASS);
CsiSetCurrentThreadPriority(THREAD_PRIORITY_TIME_CRITICAL);
// loop until the low-resolution date/time value changes (this will load the
// CPU, but only for a maximum of around 15 milliseconds)
lInitTime := Now;
lNextTime := Now;
while lNextTime = lInitTime do
lNextTime := Now;
// adjust the high-resolution performance counter frequency for clock drift
if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
QueryPerformanceCounter(lPerformanceCount) then begin
lHighResCurrentTime := fWindowsStartTime +
lPerformanceCount / fPerformanceFrequency /
SecsPerDay;
lLowResCurrentTime := Now;
if MilliSecondsBetween(lHighResCurrentTime, lLowResCurrentTime) <
(MAX_CLOCK_DIFF * 2) then
fPerformanceFrequency := Round((1 +
(lHighResCurrentTime -
lLowResCurrentTime) /
(lLowResCurrentTime - fLastSyncTime)) *
fPerformanceFrequency);
end;
// save the Windows start time by extrapolating the high-resolution
// performance counter value back to zero
if QueryPerformanceCounter(lPerformanceCount) then begin
fWindowsStartTime := lNextTime -
lPerformanceCount / fPerformanceFrequency /
SecsPerDay;
fLastSyncTime := Now;
Result := True;
end else
Result := False;
finally
CsiSetCurrentThreadPriority(lThreadPriority);
CsiSetProcessPriorityClass(lPriorityClass);
end;
end;

Related

How to calculate elapsed time of a function?

I would like to know how to calculate the time consumed for a function in Delphi.
Then I wanted to show the used time and compare it with another function or component so as to know the faster function.
You can use TStopwatch from the System.Diagnostics unit to measure elapsed time using the system's high-resolution performance counter.
var
Stopwatch: TStopwatch;
Elapsed: TTimeSpan;
....
Stopwatch := TStopwatch.StartNew;
DoSomething;
Elapsed := Stopwatch.Elapsed;
To read a time value in seconds, say, from a time span, do this:
var
Seconds: Double;
....
Seconds := Elapsed.TotalSeconds;
You can use the QueryPerformanceCounter and QueryPerformanceFrequency functions:
var
c1, c2, f: Int64;
begin
QueryPerformanceFrequency(f);
QueryPerformanceCounter(c1);
DoSomething;
QueryPerformanceCounter(c2);
// Now (c2-c1)/f is the duration in secs of DoSomething
For the sake of having more possibilities for tackling the question, you could also use System.Classes.TThread.GetTickCount to get a current time in milliseconds to start your timer before your method, and then again after your method. The difference between these two is obviously the elapsed time in milliseconds, which you could transform into hours, seconds, etc.
Having said that, David Heffernan's proposal with TStopwatch is more elegant (and more precise?).
VAR iFrequency, iTimerStart, iTimerEnd: Int64;
procedure TimerStart;
begin
if NOT QueryPerformanceFrequency(iFrequency)
then MesajWarning('High resolution timer not availalbe!');
WinApi.Windows.QueryPerformanceCounter(iTimerStart);
end;
function TimerElapsed: Double; { In miliseconds }
begin
QueryPerformanceCounter(iTimerEnd);
Result:= 1000 * ((iTimerEnd - iTimerStart) / ifrequency);
end;
function TimerElapsedS: string; { In seconds/miliseconds }
begin
if TimerElapsed < 1000
then Result:= Real2Str(TimerElapsed, 2)+ ' ms'
else Result:= Real2Str(TimerElapsed / 1000, 2)+ ' s';
end;

Dephi / Pascal Syntax (SAM Broadcaster PAL script) - How can I create a timestamp with millisecond object?

I'm a noob so please don't assume I know much. Feel free to let me know if I use incorrect termanology.
I have a function in a PAL script (based on Pascal / Delphi) in SAM broadcaster, radio automation software.
The function returns the time in milliseconds of Cue Point 1 in a database record related to a music file.
I wish to call this function's output in the body of my script as you might a variable. But it needs to be expressed as a hh:mm:ss timestamp.
Here is the function, which might have an output of 20000, for 20 seconds.
var CP : Integer = 0;
function ExtractCP(Song : TSongInfo):Integer;
var
P : Integer;
XFade : String;
begin
Result := -1;
XFade := Trim(Song['xfade']);
WriteLn('Decoding XFade string');
WriteLn('XFade: '+XFade);
if XFade = '' then
Result := -1
else
begin
P := Pos('ct0=',XFade); {Where 0 is the Custom Cue Point Number}
if (P > 0) then
begin
Delete(XFade,1,P+2);
P := Pos('&',XFade);
if (P>0) then
Delete(XFade,P,Length(XFade));
Result := StrToIntDef(XFade,-1);
WriteLn('CP time detected: '+XFade);
end;
end;
end;
Here is the implementation component.
while (Song['songtype']='S') and (not Skip) do
begin
VAR DT : DateTime;
VAR frac : Float;
VAR hours, minutes, seconds, milliseconds : Integer;
hours := 24;
minutes := 60;
seconds := 60;
milliseconds := 1000;
// 1 millisecond as fractional part of a day
frac := 1.0 / hours / minutes / seconds / milliseconds;
frac := frac * cp;
dt := Now + DateTime (frac); {Wait for Cue Point 0}
WriteLn(DateTimeToStr(dt));
PAL.WaitForTime(DT);
Skip := True;
end;
I guess my question is a simple one.
Calculated variable 'cp' is not being imported, how to I correctly call the result of the function in the line...
frac := frac * cp;
I have had help here (for full background, including timestamp calculation methodology and entire script)...
http://support.spacialaudio.com/forums/viewtopic.php?f=23&t=40795&start=15
Assuming XFade contains a String like 'ABC ct0=1234&'
Delete(XFade,1,P+2); will deliver '=1234&'
P := Pos('&',XFade);
if (P>0) then
Delete(XFade,P,Length(XFade));
will deliver '=1234' which can not be convertet to an integer
So at least you will have to change Delete(XFade,1,P+2); to Delete(XFade,1,P+3);
the generation of dt can be shortened to
{ if not defined
Const
MSecsPerDay= 24*60*60*1000;
}
cp := ExtractCP(Song);
if cp>-1 then
begin
dt := Now + CP / MSecsPerDay;
.....
end;

Delphi - How to make timer in milliseconds or nanoseconds with start/stop functions?

I am looking for a timer in milliseconds or nanoseconds in Delphi7. I have to check the speeds of three ISAM files with sequential search. The first ind file contains 50 strings like "record_0" to "record_50". The second - "record_0" to "record_500" and the third - "record_0" to "record_5000". I've implemented everything but I don't know how to make the timer. I am comparing a string with the last item in each ISAM file. Here is my code for the first ind file:
procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
var content : String[20];
var indexCounter:integer;
var keyword:string;
begin
//First ISAM file
AssignFile(indF1, 'index1.ind');
ReWrite(indF1);
Reset(indF1);
for i:=0 to 49 do begin
content := 'record_';
content := content + IntToStr(i+1);
index1.index1 := content;
index1.position1 := FileSize(indF1);
Seek(indF1, FileSize(indF1));
write(indF1, index1);
end;
CloseFile(indF1);
Label12.Caption := FileSizeStr('index1.ind');
//Sequential search in first ind file
Reset(indF1);
keyword := 'record_50';
indexCounter := 0;
//start timer
while not Eof(indF1) do begin
Seek(indF1, indexCounter);
Read(indF1, Index1);
if (keyword = Index1.index1) then begin
//stop timer;
//Label20 := milliseconds/nanoseconds;
//return/break while loop (result := -1; exit;) ???
end;
indexCounter := indexCounter + 1;
end;
I need a procedure/function so that when I call it it should start counting in milliseconds or nanoseconds and stop when the string is found (it's the last string in each ind file) and show the elapsed time for traversing through all the file. Also I don't know how to break the while loop. Thanks in advance.
The TStopWatch class described here "delphi-high-performance-timer-tstopwatch" has all functions needed (for Delphi-7).
It's implemented in later Delphi versions (Delphi-2010) as an advanced record in unit diagnostics.
Example:
var
sw : TStopWatch;
elapsedMilliseconds : cardinal;
begin
...
sw := TStopWatch.Create() ;
try
sw.Start;
while not Eof(indF1) do begin
Seek(indF1, indexCounter);
Read(indF1, Index1);
if (keyword = Index1.index1) then begin
sw.Stop;
Label20.Caption := IntToStr(sw.ElapsedMilliseconds);
break; // break while loop
end;
indexCounter := indexCounter + 1;
end;
...
finally
sw.Free;
end;
end;
To break the while loop, just do break; inside your conditional test.
Use QueryPerformanceFrequency and QueryPerformanceCounter. The first function returns a number of units per second, and the second function returns a value.
lFreq: Int64;
InitialF, FinalF: Int64;
if QueryPerformanceFrequency(lFreq) then
// hi-res timer is supported
else
// hi-res timer is not supported
QueryPerformanceCounter(InitialF);
// do something you want to time
QueryPerformanceCounter(FinalF);
// duration of the time of something is FinalF - InitialF in "units"
// divide by lFreq to get the amount of time in seconds,
// this will be an Extended type.
found this simple code:
var
StartTime : Cardinal;
begin
StartTime := GetTickCount;
//code to do
ShowMessage(Format('Elapsed time %d ms', [GetTickCount - StartTime]));
Use JclCounter from Jedi JCL. Or if you don't want to go with Jedi, use Win Api QueryPerformanceCounter.

How do I convert milliseconds to a TDateTime?

I'm doing a long loop downloading thousands of files. I would like to display an estimated time remaining, since it could take hours. However, with what I've written, I get an average number of milliseconds. How do I convert this average download time from milliseconds to a TDateTime?
See where I'm setting Label1.Caption:
procedure DoWork;
const
AVG_BASE = 20; //recent files to record for average, could be tweaked
var
Avg: TStringList; //for calculating average
X, Y: Integer; //loop iterators
TS, TE: DWORD; //tick counts
A: Integer; //for calculating average
begin
Avg:= TStringList.Create;
try
for X:= 0 to FilesToDownload.Count - 1 do begin //iterate through downloads
if FStopDownload then Break; //for cancelling
if Avg.Count >= AVG_BASE then //if list count is 20
Avg.Delete(0); //remove the oldest average
TS:= GetTickCount; //get time started
try
DownloadTheFile(X); //actual file download process
finally
TE:= GetTickCount - TS; //get time elapsed
end;
Avg.Add(IntToStr(TE)); //add download time to average list
A:= 0; //reset average to 0
for Y:= 0 to Avg.Count - 1 do //iterate through average list
A:= A + StrToIntDef(Avg[Y], 0); //add to total download time
A:= A div Avg.Count; //divide count to get average download time
Label1.Caption:= IntToStr(A); //<-- How to convert to TDateTime?
end;
finally
Avg.Free;
end;
end;
PS - I'm open to different ways of calculating the average speed of the last 20 (or AVG_BASE) downloads, because I'm sure my string list solution isn't the best. I don't want to calculate it based on all downloads, because speed may change over that time. Therefore, I'm just checking the last 20.
A TDateTime value is essentially a double, where the integer part is the number of days and fraction is the time.
In a day there are 24*60*60 = 86400 seconds (SecsPerDay constant declared in SysUtils) so to get A as TDateTime do:
dt := A/(SecsPerDay*1000.0); // A is the number of milliseconds
A better way to clock the time would be to use the TStopWatch construct in the unit Diagnostics.
Example:
sw.Create;
..
sw.Start;
// Do something
sw.Stop;
A := sw.ElapsedMilliSeconds;
// or as RRUZ suggested ts := sw.Elapsed; to get the TimeSpan
To get your average time, consider using this moving average record:
Type
TMovingAverage = record
private
FData: array of integer;
FSum: integer;
FCurrentAverage: integer;
FAddIx: integer;
FAddedValues: integer;
public
constructor Create(length: integer);
procedure Add( newValue: integer);
function Average : Integer;
end;
procedure TMovingAverage.Add(newValue: integer);
var i : integer;
begin
FSum := FSum + newValue - FData[FAddIx];
FData[FAddIx] := newValue;
FAddIx := (FAddIx + 1) mod Length(FData);
if (FAddedValues < Length(FData)) then
Inc(FAddedValues);
FCurrentAverage := FSum div FAddedValues;
end;
function TMovingAverage.Average: Integer;
begin
Result := FCurrentAverage;
end;
constructor TMovingAverage.Create(length: integer);
var
i : integer;
begin
SetLength( FData,length);
for i := 0 to length - 1 do
FData[i] := 0;
FSum := 0;
FCurrentAverage := 0;
FAddIx := 0;
FAddedValues := 0;
end;
Instead of a TDateTime you can use the TTimeSpan record, you can create a new instance passing the ticks elapsed to the constructor and from here you can use the Days, Hours, minutes, seconds and Milliseconds properties to display the elapsed time. Now for calculate the remaining time you need the total bytes to download and the current downloaded bytes.

Delphi: How to avoid EIntOverflow underflow when subtracting?

Microsoft already says, in the documentation for GetTickCount, that you could never compare tick counts to check if an interval has passed. e.g.:
Incorrect (pseudo-code):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
The above code is bad because it is suceptable to rollover of the tick counter. For example, assume that the clock is near the end of it's range:
endTime = 0xfffffe00 + 10000
= 0x00002510; //9,488 decimal
Then you perform your check:
if (GetTickCount > endTime)
Which is satisfied immediatly, since GetTickCount is larger than endTime:
if (0xfffffe01 > 0x00002510)
The solution
Instead you should always subtract the two time intervals:
DWORD startTime = GetTickCount;
...
if (GetTickCount - startTime) > 10000 //if it's been 10 seconds
break;
Looking at the same math:
if (GetTickCount - startTime) > 10000
if (0xfffffe01 - 0xfffffe00) > 10000
if (1 > 10000)
Which is all well and good in C/C++, where the compiler behaves a certain way.
But what about Delphi?
But when i perform the same math in Delphi, with overflow checking on ({Q+}, {$OVERFLOWCHECKS ON}), the subtraction of the two tick counts generates an EIntOverflow exception when the TickCount rolls over:
if (0x00000100 - 0xffffff00) > 10000
0x00000100 - 0xffffff00 = 0x00000200
What is the intended solution for this problem?
Edit: i've tried to temporarily turn off OVERFLOWCHECKS:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
But the subtraction still throws an EIntOverflow exception.
Is there a better solution, involving casts and larger intermediate variable types?
Update
Another SO question i asked explained why {$OVERFLOWCHECKS} doesn't work. It apparently only works at the function level, not the line level. So while the following doesn't work:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
the following does work:
delta := Subtract(GetTickCount, startTime);
{$OVERFLOWCHECKS OFF}]
function Subtract(const B, A: DWORD): DWORD;
begin
Result := (B - A);
end;
{$OVERFLOWCHECKS ON}
How about a simple function like this one?
function GetElapsedTime(LastTick : Cardinal) : Cardinal;
var CurrentTick : Cardinal;
begin
CurrentTick := GetTickCount;
if CurrentTick >= LastTick then
Result := CurrentTick - LastTick
else
Result := (High(Cardinal) - LastTick) + CurrentTick;
end;
So you have
StartTime := GetTickCount
...
if GetElapsedTime(StartTime) > 10000 then
...
It will work as long as the time between StartTime and the current GetTickCount is less than the infamous 49.7 days range of GetTickCount.
I have stopped doing these calculations everywhere after writing a few helper functions that are called instead.
To use the new GetTickCount64() function on Vista and later there is the following new type:
type
TSystemTicks = type int64;
which is used for all such calculations. GetTickCount() is never called directly, the helper function GetSystemTicks() is used instead:
type
TGetTickCount64 = function: int64; stdcall;
var
pGetTickCount64: TGetTickCount64;
procedure LoadGetTickCount64;
var
DllHandle: HMODULE;
begin
DllHandle := LoadLibrary('kernel32.dll');
if DllHandle <> 0 then
pGetTickCount64 := GetProcAddress(DllHandle, 'GetTickCount64');
end;
function GetSystemTicks: TSystemTicks;
begin
if Assigned(pGetTickCount64) then
Result := pGetTickCount64
else
Result := GetTickCount;
end;
// ...
initialization
LoadGetTickCount64;
end.
You could even manually track the wrap-around of the GetTickCount() return value and return a true 64 bit system tick count on earlier systems too, which should work fairly well if you call the GetSystemTicks() function at least every few days. [I seem to remember an implementation of that somewhere, but don't remember where it was. gabr posted a link and the implementation.]
Now it's trivial to implement functions like
function GetTicksRemaining(...): TSystemTicks;
function GetElapsedTicks(...): TSystemTicks;
function IsTimeRunning(...): boolean;
that will hide the details. Calling these functions instead of calculating durations in-place serves also as documentation of the code intent, so less comments are necessary.
Edit:
You write in a comment:
But like you said, the fallback on Windows 2000 and XP to GetTickCount still leaves the original problem.
You can fix this easily. First you don't need to fall back to GetTickCount() - you can use the code gabr provided to calculate a 64 bit tick count on older systems as well. (You can replace timeGetTime() with GetTickCount) if you want.)
But if you don't want to do that you can just as well disable range and overflow checks in the helper functions, or check whether the minuend is smaller than the subtrahend and correct for that by adding $100000000 (2^32) to simulate a 64 bit tick count. Or implement the functions in assembler, in which case the code doesn't have the checks (not that I would advise this, but it's a possibility).
You can also use DSiTimeGetTime64 from the DSiWin32:
threadvar
GLastTimeGetTime: DWORD;
GTimeGetTimeBase: int64;
function DSiTimeGetTime64: int64;
begin
Result := timeGetTime;
if Result < GLastTimeGetTime then
GTimeGetTimeBase := GTimeGetTimeBase + $100000000;
GLastTimeGetTime := Result;
Result := Result + GTimeGetTimeBase;
end; { DSiTimeGetTime64 }
You can use the Int64 datatype to avoid overflow:
var
Start, Delta : Int64;
begin
Start := GetTickCount;
...
Delta := GetTickCount - start;
if (Delta > 10000) then
...

Resources