Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Use GetSystemTimePreciseAsFileTime if available in DateTime.UtcNow #9736

Merged
merged 1 commit into from Feb 24, 2017

Conversation

stephentoub
Copy link
Member

@stephentoub stephentoub commented Feb 23, 2017

Makes DateTime.UtcNow much more precise, at the expense of being ~2x slower than it was previously.

For example, prior to the change, this code:

var list = new List<long>();
for (int i = 0; i < 20; i++) list.Add(DateTime.UtcNow.Ticks);
foreach (long tick in list) Console.WriteLine(tick);

produced results like:

636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888
636234535450569888

After the change, it produces results like:

636234537051614021
636234537051614039
636234537051614039
636234537051614039
636234537051614039
636234537051614042
636234537051614045
636234537051614045
636234537051614045
636234537051614045
636234537051614045
636234537051614045
636234537051614180
636234537051614180
636234537051614183
636234537051614183
636234537051614183
636234537051614183
636234537051614183
636234537051614183

Fixes https://github.com/dotnet/coreclr/issues/5061
Fixes https://github.com/dotnet/corefx/issues/16405
Closes https://github.com/dotnet/corefx/issues/15739
cc: @vancem, @jkotas

@benaadams
Copy link
Member

Can it be marked with port to desktop potential? Don't know what the procedure is on it


::GetSystemTimeAsFileTime((FILETIME*)&timestamp);
INT64 timestamp;
g_pfnGetSystemTimeAsFileTime((FILETIME*)&timestamp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - please replace the tab by spaces here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime)
{
FCALL_CONTRACT;

INT64 timestamp;
if (!g_fGetSystemTimeAsFileTimeInitialized)
Copy link
Member

@jkotas jkotas Feb 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will save a few cycles to get rid of this volatile check and move the initialization out into separate method. You can do something like this:

extern pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime;

void WINAPI InitializeGetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime)
{
    HMODULE hKernel32 = WszLoadLibrary(W("kernel32.dll")); 
    if (hKernel32 != NULL)
    {
        g_pfnGetSystemTimeAsFileTime = (pfnGetSystemTimeAsFileTime)GetProcAddress(hKernel32, "GetSystemTimePreciseAsFileTime");
    }
    else
    {
        g_pfnGetSystemTimeAsFileTime = &::GetSystemTimeAsFileTime;
    }

    g_pfnGetSystemTimeAsFileTime(lpSystemTimeAsFileTime);
}

pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime = InitializeGetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime);

The hot path can then be just g_pfnGetSystemTimeAsFileTime((FILETIME*)&timestamp);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to this general approach

@@ -41,14 +41,35 @@ typedef BOOL (*pfnGetPhoneVersion)(LPOSVERSIONINFO lpVersionInformation);
pfnGetPhoneVersion g_pfnGetPhoneVersion = NULL;
#endif

typedef void(WINAPI *pfnGetSystemTimeAsFileTime)(LPFILETIME lpSystemTimeAsFileTime);
pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime = NULL;
Volatile<bool> g_fGetSystemTimeAsFileTimeInitialized = false;

FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing that should save a few cycles is to change the signature of this method to: void GetSystemTimeAsFileTime(INT64* pTimestamp).

It will make the signature of the underlying windows API match exactly the signature of the FCall. It means that the compiler can compile the FCall to just jmp [g_pfnGetSystemTimeAsFileTime] vs. method with a frame that it is today.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning change the API signature up through to how its called from managed? Makes sense, but I'd like to leave that for a separate change, and leave this one just as the precision change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning change the API signature up through to how its called from managed?

Right.

this one just as the precision change.

ok

// Try to use GetSystemTimePreciseAsFileTime if it's available (Win8+).
// Otherwise fall back to GetSystemTimeAsFileTime.
pfnGetSystemTimeAsFileTime func = NULL;
HMODULE hKernel32 = WszLoadLibrary(W("kernel32.dll"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be under #ifndef FEATURE_PAL so that we are not trying to load kernel32.dll on Unix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@janvorli noted this to me offline as well. Fixed.

@jkotas
Copy link
Member

jkotas commented Feb 23, 2017

Can it be marked with port to desktop potential?

Marked https://github.com/dotnet/corefx/issues/15739 as netfx-port-consider

@vancem
Copy link

vancem commented Feb 23, 2017

LGTM, I would like to document the precision of DateTime.NowUtc to the degree we can. We should update the documentation comment of DateTime.NowUtc to indicate the precision you can expect (which I think is ~1us+ on both Win8+ and Linux but may be as little as 16msec on older OSes.

@stephentoub
Copy link
Member Author

@dotnet-bot test OSX x64 Checked Build and Test please
@dotnet-bot test Ubuntu x64 Checked Build and Test please
@dotnet-bot test Windows_NT x64 Debug Build and Test please

@stephentoub
Copy link
Member Author

@dotnet-bot test Ubuntu x64 Checked Build and Test please (https://github.com/dotnet/coreclr/issues/9750)

@jkotas jkotas merged commit 5ec5c2f into dotnet:master Feb 24, 2017
@mattjohnsonpint
Copy link

+1 Thanks!

@stephentoub stephentoub deleted the datetime_precise branch February 24, 2017 12:25
@mattjohnsonpint
Copy link

mattjohnsonpint commented Feb 24, 2017

What about non-windows implementations? clock_gettime(CLOCK_REALTIME) seems appropriate, IMHO.

@vancem
Copy link

vancem commented Feb 24, 2017

In Issue #5061 @mj1856 collected some useful data about what happens on Linux which I copy here (but the original has links. .

  • BTW - I did a little rough experimentation. It would seem that we are currently using the gettimeofday() function on Linux (here), which gives roughly the same precision as clock_gettime(CLOCK_REALTIME_COARSE), (and GetSystemTimeAsFileTime on Windows).

It was not completely clear what the granularity is of what we currently do however. We also don't have a clear idea of the cost of the extra precision.

However by arguments I have already made, as long as it is not super-bad, we should make the granularity on Linux at least 1msec, and preferably more like 1usec (probably by using clock_gettime(CLOCK_REALTIME).

@stephentoub
Copy link
Member Author

I'll take a look at it later today and submit a PR if needed.

@fuerstrainier
Copy link

fuerstrainier commented Sep 26, 2017

Not only is it slower that way (as stephentoub pointed out), but I also challenge the "more precise" statement.
Our Windows clock gets updated by a reliable UTC clock, which we can use to properly report on (to the authorities) and monitor, but when using GetSystemTimePreciseAsFileTime we start mixing this UTC clock with the local tick rate, which is anything but reliable. So from our perspective GetSystemTimePreciseAsFileTime yields a RANDOM timestamp between two Windows clock updates.

@stephentoub
Copy link
Member Author

@fuerstrainier, you're saying you have that problem with GetSystemTimePreciseAsFileTime but not with GetSystemTimeAsFileTime?

@fuerstrainier
Copy link

@stephentoub yes that is correct. The windows clock (which is read by GetSystemTimeAsFileTime) is updated by a proper external clock and the tick counter (additionally) used by GetSystemTimePreciseAsFileTime is updated by the local machines tick rate, which will tick faster or slower than our reference clock. If it's faster it could even overtake the windows clock updates...

@stephentoub
Copy link
Member Author

Thanks, @fuerstrainier.

@mattjohnsonpint
Copy link

@fuerstrainier - Is there a basis for your claim that it is ticking faster or slower than your reference clock? The considerable documentation on this subject would suggest otherwise.

The GetSystemTimePreciseAsFileTime docs state:

The GetSystemTimePreciseAsFileTime function retrieves the current system date and time with the highest possible level of precision (<1us). The retrieved information is in Coordinated Universal Time (UTC) format.

The Windows Server 2016 Accurate Time docs state:

Programs which require the greatest accuracy with regards to UTC, and not the passage of time, should use the GetSystemTimePreciseAsFileTime API. This assures your application gets System Time, which is conditioned by the Windows Time service.

Perhaps you were thinking of QueryPerformanceCounter such as is used in System.Diagnostics.Stopwatch? The definitive doc on that is Acquiring high-resolution time stamps, which also says to use GetSystemTimePreciseAsFileTime if you want to be bound to reference clocks, which for DateTime.UtcNow we do:

QPC is independent of and isn't synchronized to any external time reference. To retrieve time stamps that can be synchronized to an external time reference, such as, Coordinated Universal Time (UTC) for use in high-resolution time-of-day measurements, use GetSystemTimePreciseAsFileTime.

So, if you feel otherwise, I'd really like to understand why. Thanks.

@fuerstrainier
Copy link

@mj1856 Thanks for your reply. I have to amend my original statement a bit, since it basically depends on the implementation of the timeservice you are using. If it follows a "stepping" approach (using the SetSystemTime operation), you can get into troubles due to different clock speeds, but if it just "skews" the windows clock (using the SetSystemTimeAdjustment), we have no issue with time continuity. Since I learned today that using the stepping approach is not a good idea anyway, it's not a deal for us anymore. Cheers.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
9 participants