timeBeginPeriod considered harmful

Pierre A. Humblet Pierre.Humblet@ieee.org
Tue Nov 29 01:35:00 GMT 2005


At 11:10 PM 11/13/2005 -0500, Christopher Faylor wrote:
>On Sun, Nov 13, 2005 at 10:37:50PM -0500, Pierre A. Humblet wrote:
>>At 06:16 PM 11/12/2005 -0500, Christopher Faylor wrote:
>>>On Fri, Nov 11, 2005 at 05:47:17PM -0500, Pierre A. Humblet wrote:
>>>>I worked on the resolution issue a while back, to insure the following
>>>>Posix behavior (exim relies on it):
>>>>If you 1) call time() or equivalent, 2) sleep() or alarm() for some
>>>>interval, 3) call time() again, then the difference between the times in
>>>>3) and 1) cannot be smaller than the interval in 2).  Insuring that is
>>>>easier if the resolution is known.
>>>>I am not sure if Cygwin still behaves that way, insuring it requiring
>>>>rounding up at various places.
>>>
>>>I was thinking about the above in the thinking room today and it
>>>occurred to me that the right way (tm) to fix this problem would be to
>>>modify sleep/alarm so that the above is true.  alarm might be slightly
>>>trickier than sleep but I don't think it would be unbelievably hard.  It
>>>seems like adding a low_priority_sleep loop to nanosleep and alarm which
>>>waits until the time delta was >= what was expected would be preferable
>>>to forcing the resolution to 1ms for the whole program forever.
>>
>>I was just indicating what I had done with the time routines, this is not
>>directly related to the 1 ms resolution (it was there before, as I recall).
>>No matter what the resolution is, it should be possible to get it and do
>>the right thing, for example in nanosleep:
>>  DWORD resolution = gtod.resolution ();
>>  DWORD req = ((rqtp->tv_sec * 1000 + (rqtp->tv_nsec + 999999) / 1000000
>>                + resolution - 1) / resolution) * resolution;
>>Your approach can surely also be made to work.
>>I will take another look this week, on WinME and XP.
>
>Ah.  I thought you were saying that you were relying on the 1ms
>behavior.  I should have known better, though, since I have looked at
>nanosleep recently.
>
>I am not convinced that trying to factor in the resolution into the amount
>of time to sleep is going to always do the right thing but I'm not overly
>worried about it, I guess.  If the above expression is right then it should
>continue to work even when the resolution is != 1ms, AFAICT.

I made some experiments to find the clock resolution, using 
- NtQueryTimerResolution , see
http://www.sysinternals.com/Information/HighResolutionTimers.html
- GetSystemTimeAdjustment (used in sysinternals' ClockRes applet)
- the method used by hires_ms::resolution in current cvs (referred
  to as the "timeGetTime resolution" below)
- the WaitForX series
Here are my results, your mileage may vary (my program is attached)

1) The timeGetTime resolution is always equal to the rounded (up or down) 
   NtQueryTimerResolution resolution (on NT) and to the resolution actually
   obtained by WaitForX  
2) GetSystemTimeAdjustment reports an interval that is not affected by 
   timeBeginPeriod and that is never less than NtQueryTimerResolution (on
NT). 
   That makes technical sense to me (time of day clock adjustment period
   vs. timer resolution), and I am ready to consider that the timeGetTime 
   resolution is never worse than what GetSystemTimeAdjustment reports. 
3) On WinMe and Win98, the resolutions of timeGetTime and of the WaitFor
   calls is always 1 ms. This matches the documentation.
4) These resolutions are also 1 ms on my XP home SP2 (result may depend on
   other programs) 
5) On an XP Pro SP2: 
  5a) the "normal" resolution is 1/64 s (15.625 ms), but is improved to
      1/1024 s (.9765 ms) when cygwin processes such as rxvt are running.
      However for brief periods of time (a few seconds) it can change to
      1/128, 1/256, 1/512 or 1/1024 s (without Cygwin processes, of course).
  5b) The resolution is not constant inside a single process. It changes
      depending on what's happening in other processes.
  5c) timeBeginPeriod works as advertised
6) When WaitForX asks for a timeout d, the actual delay measured by
timeGetTime
   is never less than d. This is an observation. I could find no such
   statement in the Microsoft documentation, but I think it makes
   technical sense.

This is what we can conclude:
- Because of 5b), the current method in cvs for hires_ms::resolution is not
  reliable. It can err in both directions.
- If 6) is true, and because our alarm and sleep calls ultimately rely
  on WaitForX, the Posix requirement at the top of this e-mail can be met
  without calling  gtod.resolution () in nanosleep. On could simply set
  DWORD req = rqtp->tv_sec * 1000 + (rqtp->tv_nsec + 999999) / 1000000
  and get rid of hires_ms::resolution
- clock_getres should report 1 ms on 9X and probably 15.625 ms on a "standard"
  NT. Instead of hard coding 15.625 ms, I believe that one could safely use
  the interval reported by GetSystemTimeAdjustment [see 2) above]. 
  To play absolutely safe, one should report the worst resolution according
  to timeGetDevCaps.
  However if clock_setres has been called, then clock_getres should report
  that resolution.
- If we don't take 6) as being guaranteed, then we should keep 
  gtod.resolution () in nanosleep and add it in timer_thread.
  We should also keep using timeBeginPeriod to guarantee some value for
  hires_ms::resolution, or have gtod.resolution () report the same value
  as discussed above for clock_getres. 

Pierre

-------------- next part --------------
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <ntdef.h>
#include <Mmsystem.h> // Library: Winmm.lib.

// gcc clockres.c  -l winmm -l ntdll

// http://www.sysinternals.com/Information/HighResolutionTimers.html
WINAPI NTSTATUS NtQueryTimerResolution (
    PULONG MinimumResolution,
    PULONG MaximumResolution,
    PULONG ActualResolution
);

// A side effect of this function is to synchronize the caller to a tick
DWORD get_timeGetTime_resolution() {
  DWORD time1, time2, priority;
  priority = GetThreadPriority (GetCurrentThread ());
  SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);

  time1 = timeGetTime();
  do {
    time2 = timeGetTime();
  } while (time1 == time2);
  SetThreadPriority (GetCurrentThread (), priority);
  return time2 - time1;
}

DWORD get_NT_resolution() {
#ifndef Win9x
  DWORD res;
  ULONG MinRes, MaxRes, ActRes;
  res = NtQueryTimerResolution(&MinRes, &MaxRes, &ActRes);
  if (res) printf("NtStatus %lu\n", res);
  return ActRes;
#else
  return 0;
#endif
}
//----------------------------------------------------------------------
//
// Main
//
//----------------------------------------------------------------------
int main( int argc, char *argv[] )
{
   ULONG MinRes, MaxRes, ActRes;
   DWORD adjustment, clockInterval;
   BOOL  adjustmentDisabled;
   DWORD NtRes, TGTRes, Wait, NtResOld, TGTResOld, WaitOld;
   DWORD time1, time2, time3, time4;
   DWORD delay, resolution;
   DWORD res;
   MMRESULT mmres;
   int i, count;
   volatile int loop;
   const int delay_loop = 10000000;

   // Calibrate a random delay loop
   time1 = timeGetTime();
   time1 = timeGetTime();
   for (loop = 0; loop < delay_loop; loop++) {}
   time2 = timeGetTime();
   printf("Random delay value will be between 0 and %lu ms\n", time2 - time1);
   printf("Adjust so that it is greater than the resolution.\n");

   if (argc > 1)
      count = atoi(argv[1]);
   else
      count = 1;

   if (argc > 2)
      delay = atoi(argv[2]);
   else
      {
	 printf("Enter delay:\n");
	 while ((res = scanf("%lu", &delay)) != 1) {printf("%lu\n", res);};
      }

#ifndef Win9x
   res = NtQueryTimerResolution(&MinRes, &MaxRes, &ActRes);
   printf("NtStatus %lu; Min %lu Max %lu Act %lu x 100 ns\n", res, MinRes, MaxRes, ActRes);
#endif

   GetSystemTimeAdjustment( &adjustment,
			    &clockInterval,
			    &adjustmentDisabled );
   printf( "The system clock interval is %.06f ms\n",
	   (float) clockInterval / 10000 );


   // Make sure WaitFor is loaded.
   WaitForSingleObject(GetCurrentProcess(), 0);

   NtResOld = TGTResOld = WaitOld = 0;
   for (i = 0; i < count; i++)
      {
	 // Randomize time before calling WaitFor
	 for (loop = 0; loop < rand() % delay_loop; loop++) {}

	 time1 = timeGetTime();
	 res = WaitForSingleObject(GetCurrentProcess(), delay);
	 time2 = timeGetTime();
	 Wait = time2 - time1;

	 TGTRes = get_timeGetTime_resolution();
	 NtRes = get_NT_resolution();
	 if ((NtRes != NtResOld)
	     ||(TGTRes != TGTResOld)
	     ||(Wait != WaitOld))
	    {
	       if (delay > Wait)
		  printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
	       printf("%d: Nt Res %lu, timeGetTime Res %lu, WaitFor Wait %lu\n",
		      i, NtRes, TGTRes, Wait);
	       NtResOld = NtRes;
	       TGTResOld = TGTRes;
	       WaitOld = Wait;
	    }

	 Sleep(950);
      }

   // Set the resolution and watch the effect
   if (argc > 3)
      resolution = atoi(argv[3]);
   else
      {
	 printf("Enter resolution:\n");
	 while (scanf("%lu", &resolution) != 1) {};
      }

   mmres = timeBeginPeriod(resolution);
   if (mmres != TIMERR_NOERROR)
      printf("timeBeginPeriod error %d\n", mmres);
   else
      printf("\ntimeBeginPeriod success\n");

   time3 = timeGetTime();
   res = WaitForSingleObject(GetCurrentProcess(), delay);
   time4 = timeGetTime();
   if (res != WAIT_TIMEOUT)
      printf("WaitForSingleObject error %lu\n", res);

#ifndef Win9x
   res = NtQueryTimerResolution(&MinRes, &MaxRes, &ActRes);
   printf("NtStatus %lu; Min %lu Max %lu Act %lu x 100 ns\n", res, MinRes, MaxRes, ActRes);
#endif

   GetSystemTimeAdjustment( &adjustment,
			    &clockInterval,
			    &adjustmentDisabled );
   printf( "The system clock interval is %.06f ms\n",
	   (float) clockInterval / 10000 );

   printf("timeGetTime resolution %lu\n", get_timeGetTime_resolution());

   printf("WaitFor Delay %lu, Wait %lu\n", delay, time4 - time3);
   exit(0);
}


More information about the Cygwin-developers mailing list