3
\$\begingroup\$

For some purposes it can be highly desirable to use GetSystemTimePreciseAsFileTime() whenever it is available (i.e. Windows 8+) because the accuracy of GetSystemTimeAsFileTime() isn't very high and it varies from system to system. On old systems it's tied to the 15.625 ms heartbeat, but even the 1 ms of newer systems is a small eternity for a multi-core system clocked at several GHz. The full story can be read at Arno Lentfer's amazing site Microsecond Resolution Time Services for Windows and in the MSDN article Acquiring high-resolution time stamps which is also of unusually high quality.

In order to reduce hassle, the initialisation logic with GetProcAddress() etc. should be automatic and thread-safe, fully transparent to the caller. The final code will be added to a general Win32 utility unit (hence the prefix zw_) but for this post I've broken it out as a separate unit.

unit UTestGSTAFT_v1;

interface

uses
   {$ifdef VER150} Windows {$else} WinAPI.Windows {$endif};

procedure zw_GetSystemTimeAsFileTime (var ft: TFileTime);  stdcall;

implementation

procedure GSTAFT_AutoInit_ (var ft: TFileTime); stdcall; forward;

type
   TGetSystemTimeAsFileTime = procedure (var ft: TFileTime); stdcall;

var
   GetSystemTimeAsFileTime_: TGetSystemTimeAsFileTime = GSTAFT_AutoInit_;

procedure zw_GetSystemTimeAsFileTime (var ft: TFileTime);  stdcall;
begin
   GetSystemTimeAsFileTime_(ft);
end;

{$ifdef VER150}
function InterlockedCompareExchangePointer (var dst: Pointer; new_val, cmp_val: Pointer): Pointer;
stdcall; external kernel32 name 'InterlockedCompareExchange';
{$endif}

procedure GSTAFT_AutoInit_ (var ft: TFileTime); stdcall;
var
   fp: TFarProc;
begin
   fp := GetProcAddress(GetModuleHandle('kernel32'), 'GetSystemTimePreciseAsFileTime');
   if fp = nil then
      fp := @GetSystemTimeAsFileTime;
   InterlockedCompareExchangePointer(Pointer(@GetSystemTimeAsFileTime_), fp, @GSTAFT_AutoInit_);
   GetSystemTimeAsFileTime_(ft);
end;

end.

Exporting a wrapper function instead of the naked function pointer adds a bit of call overhead but that should be negligible compared to the expense of the wrapped system call. Conversely, lifting the function pointer into the interface would make the code cleaner and meaner but it would pollute the global namespace with the symbols necessary for declaring and initialising the function pointer.

Overall the wrapper function approach seems preferable since it makes the global/external situation as clean and mean as is possible at all (with the target function being the only globally visible name), and the increase in local complexity is quite manageable/affordable. The same reasoning applies to having the linker initialise the function pointer instead of resorting to the flaky practice of using the unit's initalization section for the purpose. Also, I find it preferable to see a variable initialised statically right there and then instead of having to jump to the initialization in order to verify that the variable does indeed get initialised appropriately (or to find out what it gets initialised with).

The declaration of the wrapper function was chosen to make it 1:1 compatible with the GetSystemTimeAsFileTime function found in the Windows unit shipped by Borland/Embarcadero, with all its b0rkedness. It's amazing how they managed to get the declaration of the majority of the Windows API wrong, but like it or not, it's what we have to work with and sticking with it helps to reduce confusion.

The $ifdef VER150 stuff will be replaced with appropriate prepro symbols but at the moment my compiler adaptation include file is more than ten years old. It's woefully out of date and needs a serious overhaul.

I've put a small test program on PasteBin, where the meat of the test looks like this:

function GSTAFT_resolution (iteration_count_ptr: PInteger = nil): Int64;
var
   ft0, ft1, ft2: Int64;
   i: Integer;
begin
   i := 0;
   GetSystemTimeAsFileTime(TFileTime(ft0));
   repeat
      GetSystemTimeAsFileTime(TFileTime(ft1));
   until ft1 <> ft0;
   repeat
      GetSystemTimeAsFileTime(TFileTime(ft2));
      Inc(i);
      result := ft2 - ft1;
   until result <> 0;
   if iteration_count_ptr <> nil then
      iteration_count_ptr^ := i;
end;

There's a second function exactly like this, but with zw_GetSystemTimeAsFileTime substituted for GetSystemTimeAsFileTime.

Output for Delphi 7 under WinXP/32 (XPMode run in a VMWare VM); the first row of measurements is for GetSystemTimeAsFileTime(), the second is for the zw_ variant:

4084DC:  100144 (3109338)  100144 (3092557)  100144 (3142066)  100144 (3157937)
408544:  100144 (3014201)  100144 (3039902)  100144 (3081043)  100144 (3043956)

The overhead for the wrapper and the indirect function call is clearly visible but it seems quite unproblematic, all things considered.

Output for the same binary under Win81/64:

4084F0:  10004 (280036)  10001 (273292)  10007 (225101)  10011 (226438)
408558:  3 (13)  4 (13)  3 (13)  3 (11)

It's easy to see the update interval for the standard function of 10.0144 ms under Windows XP vs. roughly 1 ms under Windows 8.1. It's also easy to see how the zw_ autoinit picks up the more precise function under Windows 8.1, which results in a resolution of 300 to 400 ns.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.