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