Jump to content
Rollo62

The status of "System.SysUtils.Now" timer resolution & accuracy

Recommended Posts

Hi there,

I'm wondering if there have been any changes to the Now() function recently, related to the timer resolution.
The Now() returnd TDateTime with Millisecond resolution, while officially they state the resolution is only 1 Sec.
https://docwiki.embarcadero.com/Libraries/Athens/en/System.SysUtils.Now

Note: Although TDateTime values can represent milliseconds, Now is accurate only to the nearest second.

This topic is quite old, so already here discussed on SO
https://stackoverflow.com/questions/14573617/how-accurate-is-delphis-now-timestamp

 

With GetTimerTick() I can reach 7-16ms timer resolution, basically.

!! And before somebody recommends high performance: I'm perfectly fine with the 15ms resolution and I want to use it.
With Now() I can see similar results on all platforms, according to my tests, which is much better than the 1 Sec. stated in the docs, pretty much like the 7-16ms resolution.

 

So what does the 1 Sec. really mean then, is this the worst-case scenario that might happen, while its perfectly 15ms all day long?

 

Perhaps there have been changed something recently, in Delphi or Windows or other Platforms?

 

Windows:
System.SysUtils.Now(): is based on  GetLocalTime(SystemTime);

https://learn.microsoft.com/de-de/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlocaltime

https://learn.microsoft.com/de-de/windows/win32/api/minwinbase/ns-minwinbase-systemtime

- I cannot find any restrictions like 1 Sec. there, not even a 15ms note.

 TThread.GetTickCount64: is based in GetTickCount64;

https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64

- Which also had no restrictions, aside the known 15ms resolution.

 


Macos/iOS: mach_absolute_time
Android/Linux: clock_gettime(CLOCK_MONOTONIC, @res);

Other Platforms were way beyond these accuracy
- All should reach 1ms without problem.

From the behaviour I can see in my tests, I would assume that Now() reaches the same 15ms resolution meanwhile on my current Win11 PC.

Is this a safe assumpttion, or are there any other known restriction, which officially state the 1 Sec. resolution?

 

This is why I think the following
- Now() and GetTickCount64 both reach about 15ms resolution
- Perhaps Now() could show resolution 1 Sec. in rare cases, e.g. under heavy load.

- If that is true, I would also expect that also GetTickCount64() might break down to 1 Sec. resolution, under heavy load.


Maybe somebody can clarify the situation, under current Windows versions, are there any official statements, aside from Embarcadero's docs?
If there were such insights for the other platforms, I'm happy to see them too, but only Windows seems to be worst here.

 

Share this post


Link to post

I work only on Windows but to get precise timings (below 1ms of accuracy) I use QueryPerformanceFrequency() and QueryPerformanceCounter().
In case precise timings are not available I switch to timeGetTime()

A messy unit is in attachment.

PS:
You can improve Windwos timeGetTime, gettickcount and sleep (DELPHI IDE does that) with:

program XXX;

uses
  System.SysUtils,
  Winapi.MMSystem,
  ...;

{$R *.res}

var
  TimeCaps: TTimeCaps;
  NeedToChangeTimerPrecsion: Boolean;

begin
  // starts high precision timer
  if timeGetDevCaps(@TimeCaps, SizeOf(TTimeCaps)) = TIMERR_NOERROR then
    NeedToChangeTimerPrecsion := timeBeginPeriod(TimeCaps.wPeriodMin) = TIMERR_NOERROR
  else
    NeedToChangeTimerPrecsion := False;

  // initializes and runs application
  Application.Initialize;
  ...;
  Application.Run;

  // stops high precision timer
  if NeedToChangeTimerPrecsion then
    timeEndPeriod(TimeCaps.wPeriodMin);
end.


 

osTimeUtils.pas

Edited by shineworld

Share this post


Link to post

Like said, I know about performance counters, but I want to use Now() and GetTimerTick64() for other tasks, accepting the 15ms tolerances.
The question is about, what the Now() 1 Sec. tolerance really means nowadays, is it gone or is it a valid case?

Edited by Rollo62

Share this post


Link to post

Mmmm,
I've tried a simple console program in Athens 12:
 

program Project4;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.DateUtils,
  Winapi.Windows;

var
  I: Integer;
  T1: Int64;
  T2: Int64;
  TD: Double;
  D1: TDateTime;
  D2: TDateTime;
  DiffMs: Int64;
  Frequency: Int64;

function Delay: Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to 1000000 do
    Inc(Result);
end;

begin
  FormatSettings.DecimalSeparator := '.';
  FormatSettings.ThousandSeparator := ',';
  try
    QueryPerformanceFrequency(Frequency);

    D1 := Now();
    QueryPerformanceCounter(T1);
    Delay;
    D2 := Now();
    QueryPerformanceCounter(T2);

    TD := (T2 - T1) * 1000 / Frequency;
    DiffMs := MilliSecondsBetween(D2, D1);
    Writeln(Format('Now() delta            = %d ms', [DiffMs]));
    Writeln(Format('QueryPerformance delta = %f ms', [TD]));


  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Result:

Now() delta            = 2 ms
QueryPerformance delta = 2.15 ms

The Delay is empirical, necessary just to have a little delay.
Now() seems to capture 2ms of delta.


Increasing delay of 10:

Now() delta            = 17 ms
QueryPerformance delta = 16.42 ms

I don't know why they report 1 sec of precision in the documentation, but if it can be so, is terribile, because my OPC UA server timings are based on TDateTime and 1ms of resolutions is already 10 time bigger than OPC-UA tick of 100ns.

Edited by shineworld
  • Like 1

Share this post


Link to post

Thanks for the proposals, but again, I'm not looking for QueryPerformanceCounter, but about the Now();  behaviour.

Moreover I'm looking for a cross-platform sulution, not Windows alone.

I dont need high performance for this task, the 15ms would be fine.
It could be even OK, if 1 Sec., if this happens only very very rare ( but I would like to avoid that ).

 

I hope that someone knows exactly where the 1 Sec. comes from and what conditions will make it to show up.

Edited by Rollo62

Share this post


Link to post

You can check System.Diagnostics.TStopWatch it is cross platform and high-precision

Share this post


Link to post

OP: Unambiguous question about the behavior of X

 

A: Here's some code that does something similar but doesn't answer your question.

 

OP: Okay but what about my question about the behavior of X

 

A: Here's some data that also doesn't answer the question.

 

OP: Thanks but...

 

B: Have you tried <something else> instead?

 

OP: *flips desk*

 

🙂

 

Apart from that, https://www.scientificamerican.com/article/time-s-passage-is-probably-an-illusion/

 

But seriously, ignore the help. It's obviously not up to date.

The behavior of Now is never going to change to match the help because that would break a lot of code and nobody needs it to have 1 second resolution.

Even in Delphi 1 (which implemented Now as Date (and Time via the DOS INT 21h, function 2Ch (which had 10 mS resolution))) the resolution was better than 1 second.

 

I too would go for TStopWatch - even if you don't need the precision.

Share this post


Link to post

Thanks, that very helpful.
It is what I expected, of course the all should be derived from a deeper hardware clock, with the same resolution.
But this is just my assumption, Raymonds article leads to this conclusion too:

Quote

Another group uses the system timer, which usually means 55ms or 10ms, although the time­Begin­Period function can be used to run the timer at a higher rate.

  • Get­Tick­Count, Get­Tick­Count64
  • Get­Message­Time
  • Get­System­Time, Get­Local­Time, Get­System­Time­As­File­Time
  • Query­Interrupt­Time, Query­Unbiased­Interrupt­Time

- GetTickCount64 is based it's same name Windows kernel function, and

- Now() is based on Windows GetLocalTime() function.

which all belongs to that same group and leads to the same timer resolution of 10 to 55ms ( I believe that is more realistic, but I will stay with 15ms the average ).


My quick measurements here show tolerances of approx. 1S = 7-8ms, where within 3S = 21-24 ms I should stay within 95% for all time resolution outliers.
But its just a rough test on a virtual machine and no scientific result, but it shows that the resolution shall be far below 1 Sec.

With a upper/lower limit of 3S I could catch only very few outliers, and within 4S it showed no more outliers at all.
Because of that I would safely assume that a tolerance of 4S = 28-32ms is a realistic worst-case scenario for my virtual machine, which matches to Raymonds note.

All in all, that is what I expected and good to know that there is at least an official statement from Microsoft too.

It may be possible that Windows system timer functions gets massively blocked by various extra loads, which may then have a good reason.
I don't expect any realtime accuracy from those kind of timers in that group anyway.

Perhaps the 1 Sec. Note from Embarcadero ist just stupid a "Warning",
that the Now() Function is not intended for any precise measurements, although it do support ms resolution.
 

10 hours ago, Anders Melander said:

OP: Unambiguous question about the behavior of X

Apart from that, https://www.scientificamerican.com/article/time-s-passage-is-probably-an-illusion/


...

I too would go for TStopWatch - even if you don't need the precision.

Yes, time is relative  ...  good that you remind me that. :classic_biggrin:
Thats why I can perfectly live with 15ms timers too, in about 99.9% of uncritical cases

 

Regarding TStopwatch.Timestamp(), this is based on the following system functions, whereas I want to avoid the macOS "mach_absolute_time()" function for obvious reasons

https://en.delphipraxis.net/topic/13126-apple-gettimertick64-mach_absolute_time-how-to-handle-the-nsprivacyaccessedapitype-privacy/?tab=comments#comment-102072

class function TStopwatch.GetTimeStamp: Int64;
//## MSWINDOWS:
  if FIsHighResolution then
    QueryPerformanceCounter(Result)
  else
    Result := GetTickCount * Int64(TTimeSpan.TicksPerMillisecond);
//## MACOS:
    Result := Int64(AbsoluteToNanoseconds( mach_absolute_time ) div 100);
//## POSIX:
    clock_gettime(CLOCK_MONOTONIC, @res);

TStopwatch relies also on GetTickCount as fallback, instead of GetTickCount64, which I hope is only a relict of older Windows versions and would not be seen on any modern ( 10yr ) PC anyway.
 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×